diff --git a/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs b/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs index 536c3aa219..3714b01868 100644 --- a/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/NativeControlHandler.cs @@ -16,27 +16,40 @@ public NativeControlHandler() { } - public void Create(object controlObject) + protected override Gtk.Widget CreateControl() { - if (controlObject == null) + if (Widget is NativeControlHost host && Callback is NativeControlHost.ICallback callback) { - Control = _eventBox; + var args = new CreateNativeControlArgs(); + callback.OnCreateNativeControl(host, args); + return CreateHost(args.NativeControl); } - else if (controlObject is Gtk.Widget widget) + return base.CreateControl(); + } + + public void Create(object nativeControl) => CreateHost(nativeControl); + + Gtk.Widget CreateHost(object nativeControl) + { + if (nativeControl == null) + { + return _eventBox; + } + 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..c766bd96c2 100644 --- a/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Mac/Forms/Controls/NativeControlHandler.cs @@ -13,9 +13,20 @@ public NativeControlHandler() { } + 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) @@ -26,25 +37,35 @@ public NativeControlHandler(NSViewController nativeControl) public override NSView ContainerControl { get { return Control; } } - public void Create(object controlObject) + public void Create(object nativeControl) { - if (controlObject == null) + Control = CreateHost(nativeControl); + } + + NSView CreateHost(object nativeControl) + { + 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..4ceb9cdc37 100644 --- a/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/NativeControlHandler.cs @@ -14,42 +14,58 @@ public NativeControlHandler() { } + 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/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..48c90e84b6 100755 --- a/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/NativeControlHandler.cs @@ -25,44 +25,60 @@ public NativeControlHandler(sw.FrameworkElement nativeControl) Control = nativeControl; } + 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..02fbf3b059 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,20 +32,97 @@ 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); + + static readonly object IsCreated_Key = new object(); + + bool IsCreated + { + get => Properties.Get(IsCreated_Key); + set => Properties.Set(IsCreated_Key, value); + } + + /// + protected override void OnVisualParentChanged(Container oldParent) + { + base.OnVisualParentChanged(oldParent); + TriggerCreateNativeControl(); + } + + /// + protected override void OnPreLoad(EventArgs e) + { + base.OnPreLoad(e); + TriggerCreateNativeControl(); + } + + void TriggerCreateNativeControl() + { + if (!IsCreated && IsSubclass) + { + var args = new CreateNativeControlArgs(); + OnCreateNativeControl(args); + Handler.Create(args.NativeControl); + IsCreated = true; + } + } + + /// + /// 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); + } } /// @@ -42,9 +132,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