From 7cb4b9ea6074538bb492f41a1ff2acb185fc054f Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Wed, 6 Dec 2023 09:28:25 -0800 Subject: [PATCH] Add ability to set up native control when subclassing NativeControlHost --- .../Forms/Controls/NativeControlHandler.cs | 35 +++++++-- src/Eto.Gtk/GtkHelpers.cs | 2 +- .../Forms/Controls/NativeControlHandler.cs | 47 ++++++++--- src/Eto.Mac/MacHelpers.cs | 4 +- .../Forms/Controls/NativeControlHandler.cs | 52 +++++++++---- src/Eto.WinForms/Forms/WindowsControl.cs | 6 +- src/Eto.WinForms/WinFormsHelpers.cs | 2 +- .../Forms/Controls/NativeControlHandler.cs | 46 ++++++++--- src/Eto/Forms/Controls/NativeControlHost.cs | 78 +++++++++++++++++-- .../UnitTests/Forms/NativeControlHostTests.cs | 24 ++++++ 10 files changed, 235 insertions(+), 61 deletions(-) diff --git a/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs b/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs index 536c3aa219..d6f2d7c337 100644 --- a/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs @@ -16,27 +16,46 @@ public NativeControlHandler() { } - public void Create(object controlObject) + protected override void Initialize() { - if (controlObject == null) + // don't call any initialize routines as we are hosting a native control + // base.Initialize(); + } + + protected override Gtk.Widget CreateControl() + { + if (Widget is NativeControlHost host && Callback is NativeControlHost.ICallback callback) + { + var args = new CreateNativeControlArgs(); + callback.OnCreateNativeControl(host, args); + return CreateHost(args.NativeControl); + } + return base.CreateControl(); + } + + public void Create(object nativeControl) => CreateHost(nativeControl); + + Gtk.Widget CreateHost(object nativeControl) + { + if (nativeControl == null) { - Control = _eventBox; + return _eventBox; } - else if (controlObject is Gtk.Widget widget) + else if (nativeControl is Gtk.Widget widget) { - Control = widget; _eventBox.Child = widget; + return _eventBox; } - else if (controlObject is IntPtr handle) + else if (nativeControl is IntPtr handle) { widget = GLib.Object.GetObject(handle) as Gtk.Widget; if (widget == null) throw new InvalidOperationException("Could not convert handle to Gtk.Widget"); - Control = widget; _eventBox.Child = widget; + return _eventBox; } else - throw new NotSupportedException($"controlObject of type {controlObject.GetType()} is not supported by this platform"); + throw new NotSupportedException($"Native control of type {nativeControl.GetType()} is not supported by this platform"); } } } diff --git a/src/Eto.Gtk/GtkHelpers.cs b/src/Eto.Gtk/GtkHelpers.cs index d164a827b3..688390904a 100644 --- a/src/Eto.Gtk/GtkHelpers.cs +++ b/src/Eto.Gtk/GtkHelpers.cs @@ -46,7 +46,7 @@ public static Control ToEto(this Gtk.Widget nativeWidget) { if (nativeWidget == null) return null; - return new Control(new NativeControlHandler(nativeWidget)); + return new NativeControlHost(nativeWidget); } /// diff --git a/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs b/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs index 51af2e0d91..49d0d2a9e6 100644 --- a/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs @@ -13,9 +13,26 @@ public NativeControlHandler() { } + protected override void Initialize() + { + // don't call any initialize routines as we are hosting a native control + base.Initialize(); + } + + protected override NSView CreateControl() + { + if (Widget is NativeControlHost host && Callback is NativeControlHost.ICallback callback) + { + var args = new CreateNativeControlArgs(); + callback.OnCreateNativeControl(host, args); + return CreateHost(args.NativeControl); + } + return base.CreateControl(); + } + public override SizeF GetPreferredSize(SizeF availableSize) { - return Control.FittingSize.ToEto(); + return Control?.FittingSize.ToEto() ?? SizeF.Empty; } public NativeControlHandler(NSViewController nativeControl) @@ -24,27 +41,37 @@ public NativeControlHandler(NSViewController nativeControl) Control = controller.View; } - public override NSView ContainerControl { get { return Control; } } + public override NSView ContainerControl => Control; - public void Create(object controlObject) + public void Create(object nativeControl) + { + Control = CreateHost(nativeControl); + } + + NSView CreateHost(object nativeControl) { - if (controlObject == null) + if (nativeControl == null) + { + return new NSView(); + } + else if (nativeControl is NSView view) { - Control = new NSView(); + return view; } - else if (controlObject is NSView view) + else if (nativeControl is NSViewController viewController) { - Control = view; + controller = viewController; + return controller.View; } - else if (controlObject is IntPtr handle) + else if (nativeControl is IntPtr handle) { view = Runtime.GetNSObject(handle) as NSView; if (view == null) throw new InvalidOperationException("supplied handle is invalid or does not refer to an object derived from NSView"); - Control = view; + return view; } else - throw new NotSupportedException($"controlObject of type {controlObject.GetType()} is not supported by this platform"); + throw new NotSupportedException($"Native control of type {nativeControl.GetType()} is not supported by this platform"); } } } diff --git a/src/Eto.Mac/MacHelpers.cs b/src/Eto.Mac/MacHelpers.cs index db9a6d0770..81fc34efae 100644 --- a/src/Eto.Mac/MacHelpers.cs +++ b/src/Eto.Mac/MacHelpers.cs @@ -66,7 +66,7 @@ public static Control ToEto(this NSView view) { if (view == null) return null; - return new Control(new NativeControlHandler(view)); + return new NativeControlHost(view); } /// @@ -78,7 +78,7 @@ public static Control ToEto(this NSViewController viewController) { if (viewController == null) return null; - return new Control(new NativeControlHandler(viewController)); + return new NativeControlHost(viewController); } /// diff --git a/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs b/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs index 76a7180195..6d385b335e 100644 --- a/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs @@ -14,42 +14,64 @@ public NativeControlHandler() { } + protected override void Initialize() + { + // don't call any initialize routines as we are hosting a native control + //base.Initialize(); + } + + protected override swf.Control CreateControl() + { + if (Widget is NativeControlHost host && Callback is NativeControlHost.ICallback callback) + { + var args = new CreateNativeControlArgs(); + callback.OnCreateNativeControl(host, args); + return CreateHost(args.NativeControl); + } + return base.CreateControl(); + } + - public void Create(object controlObject) + public void Create(object nativeControl) + { + Control = CreateHost(nativeControl); + } + swf.Control CreateHost(object nativeControl) { - if (controlObject == null) + if (nativeControl == null) { - Control = new swf.UserControl(); + return new swf.UserControl(); } - else if (controlObject is swf.Control control) + else if (nativeControl is swf.Control control) { - Control = control; + return control; } - else if (controlObject is IntPtr handle) + else if (nativeControl is IntPtr handle) { - CreateWithHandle(handle); + return CreateWithHandle(handle); } - else if (controlObject is swf.IWin32Window win32Window) + else if (nativeControl is swf.IWin32Window win32Window) { // keep a reference so it doesn't get GC'd _win32Window = win32Window; - CreateWithHandle(win32Window.Handle); + return CreateWithHandle(win32Window.Handle); } else - throw new NotSupportedException($"controlObject of type {controlObject.GetType()} is not supported by this platform"); + throw new NotSupportedException($"Native control of type {nativeControl.GetType()} is not supported by this platform"); } - private void CreateWithHandle(IntPtr handle) + private swf.Control CreateWithHandle(IntPtr handle) { - Control = new swf.Control(); + var control = new swf.Control(); Win32.GetWindowRect(handle, out var rect); - Win32.SetParent(handle, Control.Handle); - Control.Size = rect.ToSD().Size; + Win32.SetParent(handle, control.Handle); + control.Size = rect.ToSD().Size; Widget.SizeChanged += (sender, e) => { - var size = Control.Size; + var size = control.Size; Win32.SetWindowPos(handle, IntPtr.Zero, 0, 0, size.Width, size.Height, Win32.SWP.NOZORDER); }; + return control; } } } diff --git a/src/Eto.WinForms/Forms/WindowsControl.cs b/src/Eto.WinForms/Forms/WindowsControl.cs index ece795da7b..00018fb02d 100644 --- a/src/Eto.WinForms/Forms/WindowsControl.cs +++ b/src/Eto.WinForms/Forms/WindowsControl.cs @@ -184,9 +184,9 @@ protected override void OnParentBackColorChanged(EventArgs e) Control.ICallback IWindowsControl.Callback { get { return Callback; } } - public bool XScale { get; set; } + public bool XScale { get; set; } = true; - public bool YScale { get; set; } + public bool YScale { get; set; } = true; public virtual Size? GetDefaultSize(Size availableSize) { return null; }// Control.GetPreferredSize(availableSize.ToSD()).ToEto(); } @@ -276,8 +276,6 @@ public bool MouseCaptured protected override void Initialize() { base.Initialize(); - XScale = true; - YScale = true; Control.Margin = swf.Padding.Empty; Control.Tag = this; } diff --git a/src/Eto.WinForms/WinFormsHelpers.cs b/src/Eto.WinForms/WinFormsHelpers.cs index 74570ae67f..a71deb0c31 100644 --- a/src/Eto.WinForms/WinFormsHelpers.cs +++ b/src/Eto.WinForms/WinFormsHelpers.cs @@ -48,7 +48,7 @@ public static Control ToEto(this swf.Control nativeControl) { if (nativeControl == null) return null; - return new Control(new NativeControlHandler(nativeControl)); + return new NativeControlHost(nativeControl); } /// diff --git a/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs b/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs index 5c5b02d4e8..546a0fe557 100755 --- a/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs @@ -25,44 +25,66 @@ public NativeControlHandler(sw.FrameworkElement nativeControl) Control = nativeControl; } + protected override void Initialize() + { + // don't call any initialize routines as we are hosting a native control + // base.Initialize(); + } + + protected override sw.FrameworkElement CreateControl() + { + if (Widget is NativeControlHost host && Callback is NativeControlHost.ICallback callback) + { + var args = new CreateNativeControlArgs(); + callback.OnCreateNativeControl(host, args); + return CreateHost(args.NativeControl); + } + return base.CreateControl(); + } + public override Color BackgroundColor { get => throw new NotSupportedException("You cannot get this property for native controls"); set => throw new NotSupportedException("You cannot set this property for native controls"); } - public void Create(object controlObject) + public void Create(object nativeControl) + { + Control = CreateHost(nativeControl); + } + + public sw.FrameworkElement CreateHost(object nativeControl) { - if (controlObject == null) + if (nativeControl == null) { var host = new EtoHwndHost(null); host.GetPreferredSize += () => Size.Round(UserPreferredSize.ToEto() * (Widget.ParentWindow?.Screen?.LogicalPixelSize ?? 1)); - Control = host; + return host; } - else if (controlObject is sw.FrameworkElement element) + else if (nativeControl is sw.FrameworkElement element) { - Control = element; + return element; } - else if (controlObject is IntPtr handle) + else if (nativeControl is IntPtr handle) { - Control = new EtoHwndHost(new HandleRef(this, handle)); + return new EtoHwndHost(new HandleRef(this, handle)); } - else if (controlObject is IWin32WindowWinForms win32Window) + else if (nativeControl is IWin32WindowWinForms win32Window) { // keep a reference to the win32window object var host = new EtoHwndHost(new HandleRef(win32Window, win32Window.Handle)); host.GetPreferredSize += () => Size.Round(UserPreferredSize.ToEto() * (Widget.ParentWindow?.Screen?.LogicalPixelSize ?? 1)); - Control = host; + return host; } - else if (controlObject is IWin32WindowInterop win32WindowWpf) + else if (nativeControl is IWin32WindowInterop win32WindowWpf) { // keep a reference to the win32window object var host = new EtoHwndHost(new HandleRef(win32WindowWpf, win32WindowWpf.Handle)); host.GetPreferredSize += () => Size.Round(UserPreferredSize.ToEto() * (Widget.ParentWindow?.Screen?.LogicalPixelSize ?? 1)); - Control = host; + return host; } else - throw new NotSupportedException($"controlObject of type {controlObject.GetType()} is not supported by this platform"); + throw new NotSupportedException($"Native control of type {nativeControl.GetType()} is not supported by this platform"); } } diff --git a/src/Eto/Forms/Controls/NativeControlHost.cs b/src/Eto/Forms/Controls/NativeControlHost.cs index 3f50350080..5c2e7e3d13 100755 --- a/src/Eto/Forms/Controls/NativeControlHost.cs +++ b/src/Eto/Forms/Controls/NativeControlHost.cs @@ -1,5 +1,18 @@ namespace Eto.Forms; + +/// +/// Arguments when creating a native control for the +/// +public class CreateNativeControlArgs : EventArgs +{ + /// + /// Native control or handle to use. See comments for on what types are supported for each platform. + /// + /// + public object NativeControl { get; set; } +} + /// /// Control to host a native control within Eto /// @@ -19,21 +32,70 @@ public class NativeControlHost : Control new IHandler Handler => (IHandler)base.Handler; /// - /// Initializes a new instance of the native control host with the specified native controlObject + /// Initializes a new instance of the native control host with the specified native control /// - /// ControlObject to host, of null to create a native hosting control that the caller can use - public NativeControlHost(object controlObject) + /// Native contro to host, of null to create a native hosting control that the caller can use. + public NativeControlHost(object nativeControl) { - Handler.Create(controlObject); + Handler.Create(nativeControl); Initialize(); } /// /// Initializes a new instance of the native control host with a native hosting control that the caller can use directly. /// - public NativeControlHost() : this(null) + public NativeControlHost() + { + // derived classes should override OnCreateNativeControl to provide the native control + if (!IsSubclass) + Handler.Create(null); + Initialize(); + } + + bool IsSubclass => GetType() != typeof(NativeControlHost); + + /// + /// Called to create the native control. + /// + /// + /// When subclassing, override this method to create your native control and set + /// to one of the supported native control(s) for the platform you are on. + /// + /// + protected virtual void OnCreateNativeControl(CreateNativeControlArgs e) { } + + /// + /// Callback interface for the . + /// + public new interface ICallback : Control.ICallback + { + /// + /// + /// + /// + /// + void OnCreateNativeControl(NativeControlHost widget, CreateNativeControlArgs e); + } + + /// + /// Callback implementation for the + /// + protected new class Callback : Control.Callback, ICallback + { + /// + public void OnCreateNativeControl(NativeControlHost widget, CreateNativeControlArgs e) + { + using (widget.Platform.Context) + widget.OnCreateNativeControl(e); + } + } + + static readonly object callback = new Callback(); + + /// + protected override object GetCallback() => callback; /// /// Handler interface for the @@ -42,9 +104,9 @@ public NativeControlHost() : this(null) public new interface IHandler : Control.IHandler { /// - /// Initializes a new instance of the native control host with the specified native controlObject + /// Initializes a new instance of the native control host with the specified native control /// - /// ControlObject to host, of null to create a native hosting control that the caller can use - void Create(object controlObject); + /// Native contro to host, of null to create a native hosting control that the caller can use + void Create(object nativeControl); } } diff --git a/test/Eto.Test/UnitTests/Forms/NativeControlHostTests.cs b/test/Eto.Test/UnitTests/Forms/NativeControlHostTests.cs index c613f14589..4f132ae13b 100755 --- a/test/Eto.Test/UnitTests/Forms/NativeControlHostTests.cs +++ b/test/Eto.Test/UnitTests/Forms/NativeControlHostTests.cs @@ -45,5 +45,29 @@ public void NativeHostInScrollableShouldBeClipped(NativeHostTest test) return scrollable; }); } + + class MyControl : NativeControlHost + { + public NativeHostTest Test { get; set; } + + protected override void OnCreateNativeControl(CreateNativeControlArgs e) + { + base.OnCreateNativeControl(e); + e.NativeControl = Test.CreateControl(); + } + } + + [ManualTest] + [TestCaseSource(typeof(NativeHostControls), nameof(NativeHostControls.GetNativeHostTests))] + public void NetiveHostInSubclassShouldWork(NativeHostTest test) + { + ManualForm("Control should show something", form => + { + form.Resizable = true; + var control = new MyControl { Test = test }; + control.Size = new Size(100, 20); + return control; + }); + } } } \ No newline at end of file