diff --git a/src/Eto.Gtk/Forms/FormHandler.cs b/src/Eto.Gtk/Forms/FormHandler.cs index 301e2d993..67fc0d3d9 100644 --- a/src/Eto.Gtk/Forms/FormHandler.cs +++ b/src/Eto.Gtk/Forms/FormHandler.cs @@ -1,3 +1,5 @@ +using Eto.GtkSharp.CustomControls; + namespace Eto.GtkSharp.Forms { public class FormHandler : GtkWindow, Form.IHandler @@ -55,5 +57,18 @@ public bool CanFocus get { return Widget.Properties.Get(CanFocus_Key, true); } set { Widget.Properties.Set(CanFocus_Key, value, () => Control.AcceptFocus = value, true); } } + + public override bool Visible + { + get => base.Visible; + set + { + base.Visible = value; + if (ShowActivated && value) + { + Focus(); + } + } + } } } diff --git a/src/Eto.Gtk/Forms/GtkWindow.cs b/src/Eto.Gtk/Forms/GtkWindow.cs index 13efd27c4..33b1f4026 100644 --- a/src/Eto.Gtk/Forms/GtkWindow.cs +++ b/src/Eto.Gtk/Forms/GtkWindow.cs @@ -801,7 +801,7 @@ public Screen Screen } } - protected override void GrabFocus() => Control.Present(); + protected override void GrabFocus() => Control.GetWindow().Focus(0); public void BringToFront() { diff --git a/src/Eto.Wpf/Forms/ApplicationHandler.cs b/src/Eto.Wpf/Forms/ApplicationHandler.cs index 35e518c8b..d8dce0d12 100755 --- a/src/Eto.Wpf/Forms/ApplicationHandler.cs +++ b/src/Eto.Wpf/Forms/ApplicationHandler.cs @@ -1,311 +1,301 @@ using System.Windows.Threading; -namespace Eto.Wpf.Forms + +namespace Eto.Wpf.Forms; +public class ApplicationHandler : WidgetHandler, Application.IHandler { - public class ApplicationHandler : WidgetHandler, Application.IHandler + bool _attached; + bool _shutdown; + string _badgeLabel; + static ApplicationHandler _instance; + List _delayShownWindows; + static Dispatcher _dispatcher; + bool? _isActive; + + public static ApplicationHandler Instance { - bool attached; - bool shutdown; - string badgeLabel; - static ApplicationHandler instance; - List delayShownWindows; - static Dispatcher dispatcher; - - public static ApplicationHandler Instance - { - get { return instance; } - } + get { return _instance; } + } - public static bool EnableVisualStyles = true; + public static bool EnableVisualStyles = true; - /// - /// Enable custom eto-defined themes for standard or extended wpf toolkit controls. - /// - /// - /// Set this before creating the Eto Application instance. - /// - public static bool EnableCustomThemes = true; + /// + /// Enable custom eto-defined themes for standard or extended wpf toolkit controls. + /// + /// + /// Set this before creating the Eto Application instance. + /// + public static bool EnableCustomThemes = true; - public static void InvokeIfNecessary(Action action) + public static void InvokeIfNecessary(Action action) + { + if (_dispatcher == null || Thread.CurrentThread == _dispatcher.Thread) + action(); + else { - if (dispatcher == null || Thread.CurrentThread == dispatcher.Thread) - action(); - else - { - sw.Application.Current.Dispatcher.Invoke(action); - } + sw.Application.Current.Dispatcher.Invoke(action); } + } - public static T InvokeIfNecessary(Func action) + public static T InvokeIfNecessary(Func action) + { + if (_dispatcher == null || Thread.CurrentThread == _dispatcher.Thread) + return action(); + else { - if (dispatcher == null || Thread.CurrentThread == dispatcher.Thread) - return action(); - else + T ret = default(T); + _dispatcher.Invoke(new Action(() => { - T ret = default(T); - dispatcher.Invoke(new Action(() => - { - ret = action(); - })); - return ret; - } + ret = action(); + })); + return ret; } + } - public List DelayShownWindows - { - get - { - if (delayShownWindows == null) - delayShownWindows = new List(); - return delayShownWindows; - } - } + internal List DelayShownWindows => _delayShownWindows ??= new List(); - public bool IsStarted { get; private set; } + public bool IsStarted { get; private set; } - void ApplyThemes() - { - if (!EnableCustomThemes) - return; + void ApplyThemes() + { + if (!EnableCustomThemes) + return; + + // ensure the Xceed.Wpf.Toolkit assembly is loaded here. + // kind of pointless, but adding the resource dictionary below fails unless this assembly is loaded.. + var xceedWpfToolkit = typeof(Xceed.Wpf.Toolkit.ButtonSpinner).Assembly; + if (xceedWpfToolkit == null) + throw new InvalidOperationException("Could not load Xceed.Wpf.Toolkit"); + + // Add themes to our controls + var assemblyName = typeof(ApplicationHandler).Assembly.GetName().Name; + Control.Resources.MergedDictionaries.Add(new sw.ResourceDictionary { Source = new Uri($"pack://application:,,,/{assemblyName};component/themes/generic.xaml", UriKind.RelativeOrAbsolute) }); + } - // ensure the Xceed.Wpf.Toolkit assembly is loaded here. - // kind of pointless, but adding the resource dictionary below fails unless this assembly is loaded.. - var xceedWpfToolkit = typeof(Xceed.Wpf.Toolkit.ButtonSpinner).Assembly; - if (xceedWpfToolkit == null) - throw new InvalidOperationException("Could not load Xceed.Wpf.Toolkit"); + protected override void Initialize() + { + if (SynchronizationContext.Current == null) + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); - // Add themes to our controls - var assemblyName = typeof(ApplicationHandler).Assembly.GetName().Name; - Control.Resources.MergedDictionaries.Add(new sw.ResourceDictionary { Source = new Uri($"pack://application:,,,/{assemblyName};component/themes/generic.xaml", UriKind.RelativeOrAbsolute) }); + Control = sw.Application.Current; + if (Control == null) + { + Control = new sw.Application { ShutdownMode = sw.ShutdownMode.OnExplicitShutdown }; + sw.Forms.Application.EnableVisualStyles(); + Control.Startup += (s, e) => HandleStartup(); } - - protected override void Initialize() + else { - if (SynchronizationContext.Current == null) - SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); - - Control = sw.Application.Current; - if (Control == null) - { - Control = new sw.Application { ShutdownMode = sw.ShutdownMode.OnExplicitShutdown }; - sw.Forms.Application.EnableVisualStyles(); - Control.Startup += (s, e) => HandleStartup(); - } - else - { - HandleStartup(); - } + HandleStartup(); + } - // Prevent race condition with volatile font collection field in WPF when measuring a window the first time - // When running on non-english windows it can cause a NullReferenceException in System.Windows.Media.FontFamily.LookupFontFamilyAndFace - // This is a hack, but no way around it thus far.. - var temp = sw.SystemFonts.MessageFontFamily.Baseline; + // Prevent race condition with volatile font collection field in WPF when measuring a window the first time + // When running on non-english windows it can cause a NullReferenceException in System.Windows.Media.FontFamily.LookupFontFamilyAndFace + // This is a hack, but no way around it thus far.. + var temp = sw.SystemFonts.MessageFontFamily.Baseline; - dispatcher = sw.Application.Current.Dispatcher ?? Dispatcher.CurrentDispatcher; - instance = this; - ApplyThemes(); - base.Initialize(); - } + _dispatcher = sw.Application.Current.Dispatcher ?? Dispatcher.CurrentDispatcher; + _instance = this; + ApplyThemes(); + base.Initialize(); + } - void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) - { - var unhandledExceptionArgs = new UnhandledExceptionEventArgs(e.Exception, true); - Callback.OnUnhandledException(Widget, unhandledExceptionArgs); - } + void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + var unhandledExceptionArgs = new UnhandledExceptionEventArgs(e.Exception, true); + Callback.OnUnhandledException(Widget, unhandledExceptionArgs); + } - void OnCurrentDomainUnhandledException(object sender, System.UnhandledExceptionEventArgs e) - { - var unhandledExceptionArgs = new UnhandledExceptionEventArgs(e.ExceptionObject, e.IsTerminating); - Callback.OnUnhandledException(Widget, unhandledExceptionArgs); - } + void OnCurrentDomainUnhandledException(object sender, System.UnhandledExceptionEventArgs e) + { + var unhandledExceptionArgs = new UnhandledExceptionEventArgs(e.ExceptionObject, e.IsTerminating); + Callback.OnUnhandledException(Widget, unhandledExceptionArgs); + } - void HandleStartup() + void HandleStartup() + { + IsStarted = true; + Control.Activated += (sender2, e2) => Callback.OnIsActiveChanged(Widget, EventArgs.Empty); + Control.Deactivated += (sender2, e2) => Callback.OnIsActiveChanged(Widget, EventArgs.Empty); + if (_delayShownWindows != null) { - IsStarted = true; - IsActive = Win32.ApplicationIsActivated(); - Control.Activated += (sender2, e2) => IsActive = true; - Control.Deactivated += (sender2, e2) => IsActive = false; - if (delayShownWindows != null) + foreach (var window in _delayShownWindows) { - foreach (var window in delayShownWindows) - { - window.Show(); - } - delayShownWindows = null; + window.Show(); } + _delayShownWindows = null; } + } - bool _isActive; - public bool IsActive + public bool IsActive + { + get => _isActive ?? Win32.ApplicationIsActivated(); + set { - get => _isActive; - set + if (_isActive != value) { - if (_isActive != value) - { - _isActive = value; - Callback.OnIsActiveChanged(Widget, EventArgs.Empty); - } + _isActive = value; + Callback.OnIsActiveChanged(Widget, EventArgs.Empty); } } + } - public string BadgeLabel + public string BadgeLabel + { + get { return _badgeLabel; } + set { - get { return badgeLabel; } - set + _badgeLabel = value; + var mainWindow = sw.Application.Current.MainWindow; + if (mainWindow != null) { - badgeLabel = value; - var mainWindow = sw.Application.Current.MainWindow; - if (mainWindow != null) + if (mainWindow.TaskbarItemInfo == null) + mainWindow.TaskbarItemInfo = new sw.Shell.TaskbarItemInfo(); + if (!string.IsNullOrEmpty(_badgeLabel)) { - if (mainWindow.TaskbarItemInfo == null) - mainWindow.TaskbarItemInfo = new sw.Shell.TaskbarItemInfo(); - if (!string.IsNullOrEmpty(badgeLabel)) - { - // scale by the current pixel size - var m = sw.PresentationSource.FromVisual(mainWindow).CompositionTarget.TransformToDevice; - var scale = (float)m.M22; - - var bmp = GenerateBadge(scale, badgeLabel); - mainWindow.TaskbarItemInfo.Overlay = bmp; - } - else - mainWindow.TaskbarItemInfo.Overlay = null; + // scale by the current pixel size + var m = sw.PresentationSource.FromVisual(mainWindow).CompositionTarget.TransformToDevice; + var scale = (float)m.M22; + + var bmp = GenerateBadge(scale, _badgeLabel); + mainWindow.TaskbarItemInfo.Overlay = bmp; } + else + mainWindow.TaskbarItemInfo.Overlay = null; } } + } - protected virtual swm.Imaging.BitmapSource GenerateBadge(float scale, string label) + protected virtual swm.Imaging.BitmapSource GenerateBadge(float scale, string label) + { + var size = Size.Round(new SizeF(14, 14) * scale); + var bmp = new Bitmap(size, PixelFormat.Format32bppRgba); + + using (var graphics = new Graphics(bmp)) { - var size = Size.Round(new SizeF(14, 14) * scale); - var bmp = new Bitmap(size, PixelFormat.Format32bppRgba); - - using (var graphics = new Graphics(bmp)) - { - var font = SystemFonts.Bold(6 * scale); + var font = SystemFonts.Bold(6 * scale); - var textSize = graphics.MeasureString(font, label); - graphics.FillEllipse(Brushes.Red, new Rectangle(size)); + var textSize = graphics.MeasureString(font, label); + graphics.FillEllipse(Brushes.Red, new Rectangle(size)); - var pt = new PointF((bmp.Width - textSize.Width) / 2, (bmp.Height - textSize.Height - scale) / 2); - graphics.DrawText(font, Brushes.White, pt, label); - } - - return bmp.ToWpf(); + var pt = new PointF((bmp.Width - textSize.Width) / 2, (bmp.Height - textSize.Height - scale) / 2); + graphics.DrawText(font, Brushes.White, pt, label); } - public void RunIteration() - { - var frame = new DispatcherFrame(); - Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); - Dispatcher.PushFrame(frame); - WpfFrameworkElementHelper.ShouldCaptureMouse = false; - } + return bmp.ToWpf(); + } + + public void RunIteration() + { + var frame = new DispatcherFrame(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); + Dispatcher.PushFrame(frame); + WpfFrameworkElementHelper.ShouldCaptureMouse = false; + } - static object ExitFrame(object f) + static object ExitFrame(object f) + { + ((DispatcherFrame)f).Continue = false; + return null; + } + + public void Quit() + { + bool cancel = false; + foreach (sw.Window window in Control.Windows) { - ((DispatcherFrame)f).Continue = false; - return null; + window.Close(); + cancel |= window.IsVisible; } - - public void Quit() + if (!cancel) { - bool cancel = false; - foreach (sw.Window window in Control.Windows) - { - window.Close(); - cancel |= window.IsVisible; - } - if (!cancel) - { - Control.Shutdown(); - shutdown = true; - } + Control.Shutdown(); + _shutdown = true; } + } - public bool QuitIsSupported { get { return true; } } + public bool QuitIsSupported { get { return true; } } - public void Invoke(Action action) - { - ApplicationHandler.InvokeIfNecessary(action); - } + public void Invoke(Action action) + { + ApplicationHandler.InvokeIfNecessary(action); + } - public void AsyncInvoke(Action action) - { - Control.Dispatcher.BeginInvoke(action, sw.Threading.DispatcherPriority.Normal); - } + public void AsyncInvoke(Action action) + { + Control.Dispatcher.BeginInvoke(action, sw.Threading.DispatcherPriority.Normal); + } - public Keys CommonModifier - { - get { return Keys.Control; } - } + public Keys CommonModifier + { + get { return Keys.Control; } + } - public Keys AlternateModifier - { - get { return Keys.Alt; } - } + public Keys AlternateModifier + { + get { return Keys.Alt; } + } - public void Open(string url) - { - Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); - } + public void Open(string url) + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } - public void Run() + public void Run() + { + Callback.OnInitialized(Widget, EventArgs.Empty); + if (!_attached) { - Callback.OnInitialized(Widget, EventArgs.Empty); - if (!attached) + if (_shutdown) + return; + if (Widget.MainForm != null) { - if (shutdown) - return; - if (Widget.MainForm != null) - { - Control.ShutdownMode = sw.ShutdownMode.OnMainWindowClose; - Control.Run((sw.Window)Widget.MainForm.ControlObject); - } - else - { - Control.Run(); - } + Control.ShutdownMode = sw.ShutdownMode.OnMainWindowClose; + Control.Run((sw.Window)Widget.MainForm.ControlObject); + } + else + { + Control.Run(); } } + } - public void Attach(object context) - { - attached = true; - Control = sw.Application.Current; - } + public void Attach(object context) + { + _attached = true; + Control = sw.Application.Current; + } - public void OnMainFormChanged() - { - sw.Application.Current.MainWindow = Widget.MainForm.ToNative(); - } + public void OnMainFormChanged() + { + sw.Application.Current.MainWindow = Widget.MainForm.ToNative(); + } - public void Restart() - { - System.Windows.Forms.Application.Restart(); - sw.Application.Current.Shutdown(); - } + public void Restart() + { + System.Windows.Forms.Application.Restart(); + sw.Application.Current.Shutdown(); + } - public override void AttachEvent(string id) + public override void AttachEvent(string id) + { + switch (id) { - switch (id) - { - case Application.TerminatingEvent: - // handled by WpfWindow - break; - case Application.UnhandledExceptionEvent: - AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException; - sw.Application.Current.DispatcherUnhandledException += OnDispatcherUnhandledException; - break; - case Application.NotificationActivatedEvent: - // handled by NotificationHandler - break; - case Application.IsActiveChangedEvent: - // handled always - break; - default: - base.AttachEvent(id); - break; - } + case Application.TerminatingEvent: + // handled by WpfWindow + break; + case Application.UnhandledExceptionEvent: + AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException; + sw.Application.Current.DispatcherUnhandledException += OnDispatcherUnhandledException; + break; + case Application.NotificationActivatedEvent: + // handled by NotificationHandler + break; + case Application.IsActiveChangedEvent: + // handled always + break; + default: + base.AttachEvent(id); + break; } } } diff --git a/src/Eto.Wpf/Forms/FloatingFormHandler.cs b/src/Eto.Wpf/Forms/FloatingFormHandler.cs index 21fcf7ebb..e043c0ea6 100755 --- a/src/Eto.Wpf/Forms/FloatingFormHandler.cs +++ b/src/Eto.Wpf/Forms/FloatingFormHandler.cs @@ -1,80 +1,79 @@ -namespace Eto.Wpf.Forms +namespace Eto.Wpf.Forms; + +public class FloatingFormHandler : FormHandler, FloatingForm.IHandler { - public class FloatingFormHandler : FormHandler, FloatingForm.IHandler - { - static readonly object Visible_Key = new object(); - - bool _wasActive; + static readonly object Visible_Key = new object(); + + bool _wasActive; - protected override void Initialize() - { - base.Initialize(); - // defaults for a floating form - Maximizable = false; - Minimizable = false; - ShowInTaskbar = false; - Topmost = true; - } + protected override void Initialize() + { + base.Initialize(); + // defaults for a floating form + Maximizable = false; + Minimizable = false; + ShowInTaskbar = false; + Topmost = true; + } - public override void OnLoad(EventArgs e) - { - base.OnLoad(e); - Application.Instance.IsActiveChanged += Application_IsActiveChanged; - if (!Application.Instance.IsActive) - base.Visible = false; - } + public override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Application.Instance.IsActiveChanged += Application_IsActiveChanged; + if (!Application.Instance.IsActive) + base.Visible = false; + } - public override void OnUnLoad(EventArgs e) - { - base.OnUnLoad(e); - Application.Instance.IsActiveChanged -= Application_IsActiveChanged; - } + public override void OnUnLoad(EventArgs e) + { + base.OnUnLoad(e); + Application.Instance.IsActiveChanged -= Application_IsActiveChanged; + } - private void Application_IsActiveChanged(object sender, EventArgs e) + private void Application_IsActiveChanged(object sender, EventArgs e) + { + SetVisibility(true); + } + + public override bool Visible + { + get => Widget.Properties.Get(Visible_Key, false); + set { - SetVisibility(true); + Widget.Properties.Set(Visible_Key, value, false); + SetVisibility(false); } - - public override bool Visible + } + + public override void Show() + { + Widget.Properties.Set(Visible_Key, true, false); + base.Show(); + } + + void SetVisibility(bool setActive) + { + var currentlyVisible = base.Visible; + var isVisible = Application.Instance.IsActive && Visible; + if (isVisible == currentlyVisible) + return; + + if (!isVisible) { - get => Widget.Properties.Get(Visible_Key, true); - set + if (currentlyVisible) { - Widget.Properties.Set(Visible_Key, value, true); - SetVisibility(false); + _wasActive = Win32.GetThreadFocusWindow() == NativeHandle; } + base.Visible = isVisible; } - - public override void Show() + else if (setActive) { - Widget.Properties.Set(Visible_Key, true, true); - base.Show(); - } - - void SetVisibility(bool setActive) - { - var currentlyVisible = base.Visible; - var isVisible = Application.Instance.IsActive && Visible; - if (isVisible == currentlyVisible) - return; - - if (!isVisible) - { - if (currentlyVisible) - { - _wasActive = Win32.GetThreadFocusWindow() == NativeHandle; - } - base.Visible = isVisible; - } - else if (setActive) - { - var oldShowActivated = Control.ShowActivated; - Control.ShowActivated = _wasActive; - base.Visible = isVisible; - Control.ShowActivated = oldShowActivated; - } - else - base.Visible = isVisible; + var oldShowActivated = Control.ShowActivated; + Control.ShowActivated = _wasActive; + base.Visible = isVisible; + Control.ShowActivated = oldShowActivated; } + else + base.Visible = isVisible; } } \ No newline at end of file diff --git a/src/Eto.Wpf/Forms/FormHandler.cs b/src/Eto.Wpf/Forms/FormHandler.cs old mode 100644 new mode 100755 index 3ea035e20..0e4e530d1 --- a/src/Eto.Wpf/Forms/FormHandler.cs +++ b/src/Eto.Wpf/Forms/FormHandler.cs @@ -1,96 +1,105 @@ -namespace Eto.Wpf.Forms +using Eto.Wpf.Forms.Controls; + +namespace Eto.Wpf.Forms; + +public class EtoFormWindow : EtoWindow { - public class EtoFormWindow : EtoWindow + public EtoFormWindow() { - public EtoFormWindow() - { - AllowDrop = true; - } - - protected override void OnActivated(EventArgs e) - { - if (!Focusable) - return; - base.OnActivated(e); - } + AllowDrop = true; + } - protected override void OnPreviewGotKeyboardFocus(swi.KeyboardFocusChangedEventArgs e) - { - if (!Focusable) - { - e.Handled = true; - return; - } - base.OnPreviewGotKeyboardFocus(e); - } + protected override void OnActivated(EventArgs e) + { + if (!Focusable) + return; + base.OnActivated(e); } - - public class FormHandler : WpfWindow, Form.IHandler + + protected override void OnPreviewGotKeyboardFocus(swi.KeyboardFocusChangedEventArgs e) { - public FormHandler(sw.Window window) + if (!Focusable) { - Control = window; + e.Handled = true; + return; } + base.OnPreviewGotKeyboardFocus(e); + } +} - public FormHandler() - { - Control = new EtoFormWindow(); - } +public class FormHandler : WpfWindow, Form.IHandler +{ + public FormHandler(sw.Window window) + { + Control = window; + } - public virtual void Show() - { - Control.WindowStartupLocation = sw.WindowStartupLocation.Manual; - if (Control.IsLoaded) - { - Callback.OnLoadComplete(Widget, EventArgs.Empty); - FireOnLoadComplete = false; - } - else - { - // We should trigger during the Control.Loaded event - FireOnLoadComplete = true; - } - - var _ = NativeHandle; // ensure SourceInitialized is called to get right size based on style flags - - if (ApplicationHandler.Instance.IsStarted) - Control.Show(); - else - ApplicationHandler.Instance.DelayShownWindows.Add(Control); - WpfFrameworkElementHelper.ShouldCaptureMouse = false; - } + public FormHandler() + { + Control = new EtoFormWindow(); + } - protected override void InternalClosing() + public virtual void Show() + { + if (!ApplicationHandler.Instance.IsStarted) { - // Clear owner so WPF doesn't change the z-order of the parent when closing - SetOwner(null); + ApplicationHandler.Instance.DelayShownWindows.Add(this); + return; } - public bool ShowActivated + Control.WindowStartupLocation = sw.WindowStartupLocation.Manual; + if (Control.IsLoaded) { - get { return Control.ShowActivated; } - set { Control.ShowActivated = value; } + Callback.OnLoadComplete(Widget, EventArgs.Empty); + FireOnLoadComplete = false; } - - public bool CanFocus + else { - get { return Control.Focusable; } - set - { - Control.Focusable = value; - SetStyleEx(Win32.WS_EX.NOACTIVATE, !value); - SetStyle(Win32.WS.CHILD, !value); - } + // We should trigger during the Control.Loaded event + FireOnLoadComplete = true; } - public override void Focus() + var _ = NativeHandle; // ensure SourceInitialized is called to get right size based on style flags + + // Ensure it will actually be visible when shown, in the case GetPreferredSize() was called, + // or the Visibility property was set to something. + if (Control.PropertyIsInheritedOrLocal(sw.UIElement.VisibilityProperty)) + Control.Visibility = sw.Visibility.Visible; + + Control.Show(); + WpfFrameworkElementHelper.ShouldCaptureMouse = false; + } + + protected override void InternalClosing() + { + // Clear owner so WPF doesn't change the z-order of the parent when closing + SetOwner(null); + } + + public bool ShowActivated + { + get { return Control.ShowActivated; } + set { Control.ShowActivated = value; } + } + + public bool CanFocus + { + get { return Control.Focusable; } + set { - if (!Control.Focusable) - BringToFront(); - else - base.Focus(); + Control.Focusable = value; + SetStyleEx(Win32.WS_EX.NOACTIVATE, !value); + SetStyle(Win32.WS.CHILD, !value); } - - } + + public override void Focus() + { + if (!Control.Focusable) + BringToFront(); + else + base.Focus(); + } + + } \ No newline at end of file diff --git a/src/Eto.Wpf/Forms/WpfWindow.cs b/src/Eto.Wpf/Forms/WpfWindow.cs index e9e19b751..56fc96a07 100755 --- a/src/Eto.Wpf/Forms/WpfWindow.cs +++ b/src/Eto.Wpf/Forms/WpfWindow.cs @@ -138,7 +138,7 @@ protected override void Initialize() private void Control_SourceInitialized(object sender, EventArgs e) { isSourceInitialized = true; - + if (Resizable && WindowStyle == WindowStyle.None) { SetWindowChrome(); @@ -179,16 +179,15 @@ private IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref private void Control_Loaded(object sender, sw.RoutedEventArgs e) { - // NOTE: If the window size is set, it will be made visible BEFORE this is called. - SetMinimumSize(); if (initialClientSize != null) { initialClientSize = null; SetContentSize(); } - // stop form from auto-sizing after it is shown - Control.SizeToContent = sw.SizeToContent.Manual; + + // Set sizing mode - if it is set to manual before here it will be visible before it is loaded + // which we do not want. SetSizeToContent(); if (Control.ShowActivated) Control.MoveFocus(new swi.TraversalRequest(swi.FocusNavigationDirection.Next)); @@ -389,7 +388,7 @@ protected override void SetSize() private void SetSizeToContent() { sw.SizeToContent sizing; - if (Widget.Loaded && !AutoSize) + if (Control.IsLoaded && !AutoSize) { sizing = sw.SizeToContent.Manual; } @@ -413,6 +412,11 @@ private void SetSizeToContent() } } + // If we set it to manual before loaded, it gets shown before Loaded event fires, + // which we do not want. + if (sizing == sw.SizeToContent.Manual && !Control.IsLoaded) + return; + Control.SizeToContent = sizing; } diff --git a/src/Eto/Forms/Form.cs b/src/Eto/Forms/Form.cs old mode 100644 new mode 100755 index fd00d5b79..e17fc5217 --- a/src/Eto/Forms/Form.cs +++ b/src/Eto/Forms/Form.cs @@ -79,7 +79,7 @@ public override bool Visible } /// - /// Show the form + /// Show the form, and make it the active window if is true /// public void Show() { @@ -89,9 +89,13 @@ public void Show() OnPreLoad(EventArgs.Empty); OnLoad(EventArgs.Empty); Application.Instance.AddWindow(this); + Handler.Show(); + } + else + { + // Already shown, make it visible + base.Visible = true; } - - Handler.Show(); } /// diff --git a/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj b/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj index a19af1a4f..5b1a6bd97 100644 --- a/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj +++ b/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj @@ -36,6 +36,11 @@ + + + + + diff --git a/test/Eto.Test.Wpf/UnitTests/NativeParentWindowTests.cs b/test/Eto.Test.Wpf/UnitTests/NativeParentWindowTests.cs index 64c6da902..763eba8e0 100644 --- a/test/Eto.Test.Wpf/UnitTests/NativeParentWindowTests.cs +++ b/test/Eto.Test.Wpf/UnitTests/NativeParentWindowTests.cs @@ -1,7 +1,5 @@ using Eto.Test.UnitTests; using NUnit.Framework; -using sw = System.Windows; -using swc = System.Windows.Controls; using swf = System.Windows.Forms; diff --git a/test/Eto.Test.Wpf/UnitTests/WindowTests.cs b/test/Eto.Test.Wpf/UnitTests/WindowTests.cs new file mode 100755 index 000000000..ca2207c5e --- /dev/null +++ b/test/Eto.Test.Wpf/UnitTests/WindowTests.cs @@ -0,0 +1,30 @@ +using Eto.Test.UnitTests; +using Eto.Wpf.Forms; +using NUnit.Framework; + +namespace Eto.Test.Wpf.UnitTests; + +[TestFixture] +public class WindowTests : TestBase +{ + [Test] + public void WindowShouldActuallyBeVisibleWhenShown() => Async(async () => { + + var form = new Form { Content = new Panel { Content = "Hello", Size = new Size(200, 200) } }; + + // This resets the Visibility of the WPF window so it can cause the form not to show + var size = form.GetPreferredSize(); + + form.Show(); + + await Task.Delay(100); + + var handler = form.Handler as FormHandler; + + Assert.That(handler.Control.Visibility, Is.EqualTo(sw.Visibility.Visible), "#1.1 Visibility should be visible"); + Assert.That(handler.Control.IsActive, Is.EqualTo(true), "#1.2 Form should be active"); + + form.Close(); + }); + +} diff --git a/test/Eto.Test/Sections/Behaviors/WindowsSection.cs b/test/Eto.Test/Sections/Behaviors/WindowsSection.cs old mode 100644 new mode 100755 index 927a6dd65..d05367b0a --- a/test/Eto.Test/Sections/Behaviors/WindowsSection.cs +++ b/test/Eto.Test/Sections/Behaviors/WindowsSection.cs @@ -29,7 +29,7 @@ public WindowsSection() var typeControls = CreateTypeControls(); - layout.AddSeparateRow(null, Resizable(), Minimizable(), Maximizable(), MovableByWindowBackground(), null); + layout.AddSeparateRow(null, Resizable(), AutoSize(), Minimizable(), Maximizable(), MovableByWindowBackground(), null); layout.AddSeparateRow(null, ShowInTaskBar(), CloseableCheckBox(), TopMost(), VisibleCheckbox(), CreateShowActivatedCheckbox(), CreateCanFocus(), null); layout.AddSeparateRow(null, "Type", typeControls, null); layout.AddSeparateRow(null, "Window Style", WindowStyle(), null); @@ -62,6 +62,7 @@ enum WindowType class SettingsWindow : INotifyPropertyChanged { public bool ThreeState => true; // enable three state for these settings + public bool? AutoSize { get; set; } public bool? Resizable { get; set; } public bool? CanFocus { get; set; } public bool? Minimizable { get; set; } @@ -187,7 +188,7 @@ Control WindowStyle() Control DisplayModeDropDown() { dialogDisplayModeDropDown = new EnumDropDown(); - dialogDisplayModeDropDown.Bind(c => c.Enabled, typeRadio, Binding.Property((RadioButtonList t) => t.SelectedKey).ToBool("dialog")); + dialogDisplayModeDropDown.Bind(c => c.Enabled, typeRadio, Binding.Property((EnumRadioButtonList t) => t.SelectedValue).ToBool(WindowType.Dialog)); dialogDisplayModeDropDown.SelectedValueChanged += (sender, e) => { if (child is Dialog dlg) @@ -217,6 +218,13 @@ Control Resizable() resizableCheckBox.CheckedBinding.BindDataContext((Window w) => w.Resizable); return resizableCheckBox; } + Control AutoSize() + { + var resizableCheckBox = new CheckBox { Text = "AutoSize" }; + resizableCheckBox.BindDataContext(c => c.ThreeState, (SettingsWindow w) => w.ThreeState); + resizableCheckBox.CheckedBinding.BindDataContext((Window w) => w.AutoSize); + return resizableCheckBox; + } Control Maximizable() { @@ -510,6 +518,8 @@ void CreateChild() child.Topmost = settings.Topmost ?? false; if (settings.Resizable != null) child.Resizable = settings.Resizable ?? false; + if (settings.AutoSize != null) + child.AutoSize = settings.AutoSize ?? false; if (settings.Maximizable != null) child.Maximizable = settings.Maximizable ?? false; if (settings.Minimizable != null) diff --git a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs old mode 100644 new mode 100755 index 74daf9909..cd4095759 --- a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs @@ -57,6 +57,7 @@ public void ControlShouldFireShownEventWhenAddedDynamically(IControlTypeInfo { + form.Size = new Size(200, 200); form.Shown += (sender, e) => Application.Instance.AsyncInvoke(() => { try diff --git a/test/Eto.Test/UnitTests/Forms/FormTests.cs b/test/Eto.Test/UnitTests/Forms/FormTests.cs index d3695729b..578275f46 100644 --- a/test/Eto.Test/UnitTests/Forms/FormTests.cs +++ b/test/Eto.Test/UnitTests/Forms/FormTests.cs @@ -1,124 +1,174 @@ using NUnit.Framework; -namespace Eto.Test.UnitTests.Forms +namespace Eto.Test.UnitTests.Forms; + +[TestFixture] +public class FormTests : WindowTests
{ - [TestFixture] - public class FormTests : WindowTests + protected override void Test(Action test, int timeout = 4000) => Form(test, timeout); + protected override void ManualTest(string message, Func test) => ManualForm(message, test); + protected override void Show(Form window) => window.Show(); + protected override Task ShowAsync(Form window) { - protected override void Test(Action test, int timeout = 4000) => Form(test, timeout); - protected override void ManualTest(string message, Func test) => ManualForm(message, test); - protected override void Show(Form window) => window.Show(); - protected override Task ShowAsync(Form window) - { - var tcs = new TaskCompletionSource(); - window.Closed += (sender, e) => tcs.TrySetResult(true); - window.Show(); - return tcs.Task; - } - - [Test, ManualTest] - public void WindowShouldCloseOnLostFocusWithoutHidingParent() + var tcs = new TaskCompletionSource(); + window.Closed += (sender, e) => tcs.TrySetResult(true); + window.Show(); + return tcs.Task; + } + + [Test, ManualTest] + public void WindowShouldCloseOnLostFocusWithoutHidingParent() + { + ManualForm("Click on this window after the child is shown,\nthe form and the main form should not go behind other windows", + form => { - ManualForm("Click on this window after the child is shown,\nthe form and the main form should not go behind other windows", - form => + var content = new Panel { MinimumSize = new Size(100, 100) }; + form.Shown += (sender, e) => { - var content = new Panel { MinimumSize = new Size(100, 100) }; - form.Shown += (sender, e) => + var childForm = new Form { - var childForm = new Form - { - Title = "Child Form", - ClientSize = new Size(100, 100), - Owner = form - }; - childForm.MouseDown += (s2, e2) => childForm.Close(); - childForm.LostFocus += (s2, e2) => childForm.Close(); - childForm.Show(); + Title = "Child Form", + ClientSize = new Size(100, 100), + Owner = form }; - form.Title = "Test Form"; - form.Owner = Application.Instance.MainForm; - return content; - } - ); + childForm.MouseDown += (s2, e2) => childForm.Close(); + childForm.LostFocus += (s2, e2) => childForm.Close(); + childForm.Show(); + }; + form.Title = "Test Form"; + form.Owner = Application.Instance.MainForm; + return content; } - - // Hm, this seems useful.. should it be added as an extension method somewhere? - static Task EventAsync(TWidget control, Action> addHandler, Action> removeHandler = null) - where TWidget : Widget - { - var mre = new TaskCompletionSource(); - void EventTriggered(object sender, TEvent e) - { - removeHandler?.Invoke(control, EventTriggered); - mre.TrySetResult(true); - } + ); + } - addHandler(control, EventTriggered); - return mre.Task; + // Hm, this seems useful.. should it be added as an extension method somewhere? + static Task EventAsync(TWidget control, Action> addHandler, Action> removeHandler = null) + where TWidget : Widget + { + var mre = new TaskCompletionSource(); + void EventTriggered(object sender, TEvent e) + { + removeHandler?.Invoke(control, EventTriggered); + mre.TrySetResult(true); } - - [Test, ManualTest] - public void MultipleChildWindowsShouldGetFocusWhenClicked() => Async(async () => + + addHandler(control, EventTriggered); + return mre.Task; + } + + [Test, ManualTest] + public void MultipleChildWindowsShouldGetFocusWhenClicked() => Async(async () => + { + var form1 = new Form { ClientSize = new Size(200, 200), Location = new Point(300, 300) }; + form1.Owner = Application.Instance.MainForm; + form1.Title = "Form1"; + form1.Content = new Label { - var form1 = new Form { ClientSize = new Size(200, 200), Location = new Point(300, 300) }; - form1.Owner = Application.Instance.MainForm; - form1.Title = "Form1"; - form1.Content = new Label - { - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - Text = "Click on Form2, it should then get focus and be on top of this form." - }; - // var form1ClosedTask = EventTask(h => form1.Closed += h); - var form1ClosedTask = EventAsync(form1, (c, h) => c.Closed += h); + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Text = "Click on Form2, it should then get focus and be on top of this form." + }; + // var form1ClosedTask = EventTask(h => form1.Closed += h); + var form1ClosedTask = EventAsync(form1, (c, h) => c.Closed += h); - var form2 = new Form { ClientSize = new Size(200, 200), Location = new Point(400, 400) }; - form2.Owner = Application.Instance.MainForm; - form2.Title = "Form2"; - form2.Content = new Label - { - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - Text = "Click on Form1, it should then get focus and be on top of this form." - }; - var form2ClosedTask = EventAsync(form2, (c, h) => c.Closed += h); + var form2 = new Form { ClientSize = new Size(200, 200), Location = new Point(400, 400) }; + form2.Owner = Application.Instance.MainForm; + form2.Title = "Form2"; + form2.Content = new Label + { + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Text = "Click on Form1, it should then get focus and be on top of this form." + }; + var form2ClosedTask = EventAsync(form2, (c, h) => c.Closed += h); - form1.Show(); + form1.Show(); - form2.Show(); + form2.Show(); - // wait till both forms are closed.. - await Task.WhenAll(form1ClosedTask, form2ClosedTask); - }); - - public class SubSubForm : SubForm + // wait till both forms are closed.. + await Task.WhenAll(form1ClosedTask, form2ClosedTask); + }); + + public class SubSubForm : SubForm + { + protected override void OnClosed(EventArgs e) { - protected override void OnClosed(EventArgs e) - { - base.OnClosed(e); - } + base.OnClosed(e); } + } - public class SubForm : Form + public class SubForm : Form + { + protected override void OnClosed(EventArgs e) { - protected override void OnClosed(EventArgs e) - { - base.OnClosed(e); - } + base.OnClosed(e); } + } - [Test] - public void ClosedEventShouldFireOnceWithMultipleSubclasses() + [Test] + public void ClosedEventShouldFireOnceWithMultipleSubclasses() + { + int closed = 0; + Form(form => { - int closed = 0; - Form(form => - { - form.Content = new Panel { Size = new Size(300, 300) }; - form.Closed += (sender, e) => closed++; - form.Shown += (sender, e) => form.Close(); - }); - Assert.AreEqual(1, closed, "Closed event should only fire once"); - } - + form.Content = new Panel { Size = new Size(300, 300) }; + form.Closed += (sender, e) => closed++; + form.Shown += (sender, e) => form.Close(); + }); + Assert.AreEqual(1, closed, "Closed event should only fire once"); } -} + [TestCase(true)] + [TestCase(false)] + [ManualTest] + public void CallingShowTwiceShouldWork(bool showActivated) => Async(async () => + { + var form = new Form(); + + form.Content = "Click on this form. It should hide then show again"; + form.Size = new Size(200, 200); + form.ShowActivated = showActivated; + + form.Show(); + + var tcs = new TaskCompletionSource(false); + + form.MouseDown += async (sender, e) => + { + form.Visible = false; + await Task.Delay(1000); + form.Show(); + }; + + form.Closed += (sender, e) => tcs.SetResult(true); + + await tcs.Task; + }); + + [Test] + [ManualTest] + public void CallingShowAfterShownShouldNotBringItTopMost() => Async(async () => + { + var form = new Form(); + + form.Content = "Click on the main form. This form should not come back on top."; + form.Size = new Size(200, 200); + + form.Show(); + + var tcs = new TaskCompletionSource(false); + + form.LostFocus += async (sender, e) => + { + await Task.Delay(1000); + form.Show(); + await Task.Delay(1000); + form.Close(); + }; + + form.Closed += (sender, e) => tcs.SetResult(true); + + await tcs.Task; + }); +} \ No newline at end of file diff --git a/test/Eto.Test/UnitTests/Forms/WindowTests.cs b/test/Eto.Test/UnitTests/Forms/WindowTests.cs old mode 100644 new mode 100755 index eaef5985f..10dc66ee9 --- a/test/Eto.Test/UnitTests/Forms/WindowTests.cs +++ b/test/Eto.Test/UnitTests/Forms/WindowTests.cs @@ -1,360 +1,397 @@ using NUnit.Framework; -namespace Eto.Test.UnitTests.Forms + +namespace Eto.Test.UnitTests.Forms; + +public abstract class WindowTests : TestBase + where T : Window, new() { - public abstract class WindowTests : TestBase - where T : Window, new() + protected abstract void Test(Action test, int timeout = DefaultTimeout); + protected abstract void ManualTest(string message, Func test); + protected abstract void Show(T window); + protected abstract Task ShowAsync(T window); + + [Test] + [ManualTest] + public void WindowShouldAutoSize() => Test(window => + { + window.AutoSize = true; + window.MinimumSize = Size.Empty; + + var bottomPanel = new StackLayout(); + var rightPanel = new StackLayout { Orientation = Orientation.Horizontal }; + + var autoSize = new CheckBox { Text = "AutoSize", Checked = window.AutoSize }; + autoSize.CheckedChanged += (sender, e) => + { + window.AutoSize = autoSize.Checked == true; + }; + + var addBottomButton = new Button { Text = "Add bottom control" }; + addBottomButton.Click += (sender, e) => + { + bottomPanel.Items.Add(new Panel { Size = new Size(20, 20) }); + autoSize.Checked = window.AutoSize; + }; + + var addRightButton = new Button { Text = "Add right control" }; + addRightButton.Click += (sender, e) => + { + rightPanel.Items.Add(new Panel { Size = new Size(20, 20) }); + autoSize.Checked = window.AutoSize; + }; + + var resetButton = new Button { Text = "Reset" }; + resetButton.Click += (sender, e) => + { + window.SuspendLayout(); + bottomPanel.Items.Clear(); + rightPanel.Items.Clear(); + window.ResumeLayout(); + autoSize.Checked = window.AutoSize; + }; + + window.SizeChanged += (sender, e) => autoSize.Checked = window.AutoSize; + + var layout = new DynamicLayout(); + layout.BeginHorizontal(); + layout.BeginCentered(); + layout.Add(addRightButton); + layout.Add(addBottomButton); + layout.Add(resetButton); + layout.Add(autoSize); + layout.EndCentered(); + layout.Add(rightPanel); + layout.EndHorizontal(); + layout.Add(bottomPanel); + + window.Content = layout; + }, -1); + + + + [ManualTest] + [TestCase(true, true, false)] + [TestCase(true, false, true)] + [TestCase(false, true, false)] + [TestCase(false, false, true)] + public void WindowShouldHaveCorrectInitialSizeWithWrappedLabel(bool useSize, bool setWidth, bool setHeight) => Async(async () => { - protected abstract void Test(Action test, int timeout = DefaultTimeout); - protected abstract void ManualTest(string message, Func test); - protected abstract void Show(T window); - protected abstract Task ShowAsync(T window); - - [ManualTest] - public void WindowShouldAutoSize() => Test(window => + bool wasClicked = false; + + const string infoText = "Click to change text.\n"; + var label = new Label(); + label.TextColor = Colors.White; + label.Text = infoText + Utility.LoremTextWithTwoParagraphs; + + T window = new T(); + if (window is Form form) + form.ShowActivated = false; + window.AutoSize = true; + window.BackgroundColor = Colors.Blue; + window.Resizable = false; + window.Content = label; + + if (useSize) + { + if (setWidth && setHeight) + window.Size = new Size(150, 150); + else if (setWidth) + window.Size = new Size(150, -1); + else if (setHeight) + window.Size = new Size(-1, 150); + } + else { - window.AutoSize = true; - window.MinimumSize = Size.Empty; + if (setWidth && setHeight) + window.Width = window.Height = 150; + else if (setWidth) + window.Width = 150; + else if (setHeight) + window.Height = 150; + } - var bottomPanel = new StackLayout(); - var rightPanel = new StackLayout { Orientation = Orientation.Horizontal }; + label.MouseDown += (sender, e) => + { + label.Text = infoText + Utility.GenerateLoremText(new Random().Next(200)); + wasClicked = true; + }; - var autoSize = new CheckBox { Text = "AutoSize", Checked = window.AutoSize }; - autoSize.CheckedChanged += (sender, e) => - { - window.AutoSize = autoSize.Checked == true; - }; + window.Owner = Application.Instance.MainForm; + await ShowAsync(window); + Assert.IsTrue(wasClicked, "#1 You need to click on it to confirm it is resized correctly"); + }); - var addBottomButton = new Button { Text = "Add bottom control" }; - addBottomButton.Click += (sender, e) => - { - bottomPanel.Items.Add(new Panel { Size = new Size(20, 20) }); - autoSize.Checked = window.AutoSize; - }; + [Test] + public void WindowShouldReportInitialSize() + { + Size? size = null; + Shown(form => + { + form.Content = new Panel { Size = new Size(300, 300) }; + form.SizeChanged += (sender, e) => size = form.Size; + }, + () => + { + Assert.IsNotNull(size, "#1"); + Assert.IsTrue(size.Value.Width >= 300, "#2"); + Assert.IsTrue(size.Value.Height >= 300, "#3"); + }); + } - var addRightButton = new Button { Text = "Add right control" }; - addRightButton.Click += (sender, e) => - { - rightPanel.Items.Add(new Panel { Size = new Size(20, 20) }); - autoSize.Checked = window.AutoSize; - }; + [Test] + public void DefaultFormValuesShouldBeCorrect() + { + TestProperties(f => f, + f => f.CanFocus, + f => f.ShowActivated, + f => f.Enabled + ); + } - var resetButton = new Button { Text = "Reset" }; - resetButton.Click += (sender, e) => - { - window.SuspendLayout(); - bottomPanel.Items.Clear(); - rightPanel.Items.Clear(); - window.ResumeLayout(); - autoSize.Checked = window.AutoSize; - }; - - window.SizeChanged += (sender, e) => autoSize.Checked = window.AutoSize; - - var layout = new DynamicLayout(); - layout.BeginHorizontal(); - layout.BeginCentered(); - layout.Add(addRightButton); - layout.Add(addBottomButton); - layout.Add(resetButton); - layout.Add(autoSize); - layout.EndCentered(); - layout.Add(rightPanel); - layout.EndHorizontal(); - layout.Add(bottomPanel); - - window.Content = layout; - }, -1); - - - - [ManualTest] - [TestCase(true, true, false)] - [TestCase(true, false, true)] - [TestCase(false, true, false)] - [TestCase(false, false, true)] - public void WindowShouldHaveCorrectInitialSizeWithWrappedLabel(bool useSize, bool setWidth, bool setHeight) => Async(async () => + [TestCase(true)] + [TestCase(false)] + [ManualTest] + public void InitialLocationOfWindowShouldBeCorrect(bool withOwner) + { + ManualTest("This window should be located at the top left of the screen", form => { - bool wasClicked = false; - - const string infoText = "Click to change text.\n"; - var label = new Label(); - label.TextColor = Colors.White; - label.Text = infoText + Utility.LoremTextWithTwoParagraphs; - - T window = new T(); - if (window is Form form) - form.ShowActivated = false; - window.AutoSize = true; - window.BackgroundColor = Colors.Blue; - window.Resizable = false; - window.Content = label; - - if (useSize) - { - if (setWidth && setHeight) - window.Size = new Size(150, 150); - else if (setWidth) - window.Size = new Size(150, -1); - else if (setHeight) - window.Size = new Size(-1, 150); - } - else + if (withOwner) + form.Owner = Application.Instance.MainForm; + form.Location = new Point(0, 0); + + return new Panel { Size = new Size(200, 200) }; + }); + } + + [TestCase(-1)] + [TestCase(100)] // fails on Mac and Wpf currently, due to the bottom label being wider than this size... + [TestCase(250)] + [ManualTest] + public void SizeOfFormShouldWorkWithLabels(int width) + { + ManualTest("Window should not have large space at\nthe bottom or between labels", form => + { + Label CreateLabel() { - if (setWidth && setHeight) - window.Width = window.Height = 150; - else if (setWidth) - window.Width = 150; - else if (setHeight) - window.Height = 150; + var label = new Label { Text = Utility.GenerateLoremText(20) }; + if (width > 0) + label.Width = width; + return label; } - label.MouseDown += (sender, e) => - { - label.Text = infoText + Utility.GenerateLoremText(new Random().Next(200)); - wasClicked = true; - }; + var layout = new TableLayout(); + layout.Rows.Add(CreateLabel()); + layout.Rows.Add(CreateLabel()); + layout.Rows.Add(CreateLabel()); + layout.Rows.Add(CreateLabel()); - window.Owner = Application.Instance.MainForm; - await ShowAsync(window); - Assert.IsTrue(wasClicked, "#1 You need to click on it to confirm it is resized correctly"); + return layout; }); + } - [Test] - public void WindowShouldReportInitialSize() + [Test, ManualTest] + public void WindowFromPointShouldReturnWindowUnderPoint() + { + ManualTest("Move your mouse, it should show the title of the window under the mouse pointer", + form => { - Size? size = null; - Shown(form => - { - form.Content = new Panel { Size = new Size(300, 300) }; - form.SizeChanged += (sender, e) => size = form.Size; - }, - () => - { - Assert.IsNotNull(size, "#1"); - Assert.IsTrue(size.Value.Width >= 300, "#2"); - Assert.IsTrue(size.Value.Height >= 300, "#3"); - }); - } - - [Test] - public void DefaultFormValuesShouldBeCorrect() + var content = new Panel { MinimumSize = new Size(100, 100) }; + var timer = new UITimer { Interval = 0.5 }; + timer.Elapsed += (sender, e) => + { + var window = Window.FromPoint(Mouse.Position); + content.Content = $"Window: {window?.Title}"; + }; + timer.Start(); + form.Closed += (sender, e) => { - TestProperties(f => f, - f => f.CanFocus, - f => f.ShowActivated, - f => f.Enabled - ); + timer.Stop(); + }; + form.Title = "Test Form"; + return content; } + ); + } - [TestCase(true)] - [TestCase(false)] - [ManualTest] - public void InitialLocationOfWindowShouldBeCorrect(bool withOwner) - { - ManualTest("This window should be located at the top left of the screen", form => - { - if (withOwner) - form.Owner = Application.Instance.MainForm; - form.Location = new Point(0, 0); + [TestCase(200, 200, WindowStyle.Default)] + [TestCase(300, 200, WindowStyle.Default)] + [TestCase(200, 300, WindowStyle.Default)] + [TestCase(20, 20, WindowStyle.None)] + [TestCase(100, 100, WindowStyle.None)] + [TestCase(200, 200, WindowStyle.None)] + [TestCase(200, 100, WindowStyle.None)] + [TestCase(200, 100, WindowStyle.Utility)] + [TestCase(300, 200, WindowStyle.Utility)] + [TestCase(200, 300, WindowStyle.Utility)] + public void GetPreferredSizeShouldWorkInOnLoadComplete(int width, int height, WindowStyle style) + { + GetPreferredSizeShouldWorkHelper(width, height, style, true); + } - return new Panel { Size = new Size(200, 200) }; - }); - } - [TestCase(-1)] - [TestCase(100)] // fails on Mac and Wpf currently, due to the bottom label being wider than this size... - [TestCase(250)] - [ManualTest] - public void SizeOfFormShouldWorkWithLabels(int width) - { - ManualTest("Window should not have large space at\nthe bottom or between labels", form => - { - Label CreateLabel() - { - var label = new Label { Text = Utility.GenerateLoremText(20) }; - if (width > 0) - label.Width = width; - return label; - } - - var layout = new TableLayout(); - layout.Rows.Add(CreateLabel()); - layout.Rows.Add(CreateLabel()); - layout.Rows.Add(CreateLabel()); - layout.Rows.Add(CreateLabel()); - - return layout; - }); - } + [TestCase(200, 200, WindowStyle.Default)] + [TestCase(300, 200, WindowStyle.Default)] + [TestCase(200, 300, WindowStyle.Default)] + [TestCase(20, 20, WindowStyle.None)] + [TestCase(100, 100, WindowStyle.None)] + [TestCase(200, 200, WindowStyle.None)] + [TestCase(200, 100, WindowStyle.None)] + [TestCase(200, 100, WindowStyle.Utility)] + [TestCase(300, 200, WindowStyle.Utility)] + [TestCase(200, 300, WindowStyle.Utility)] + public void GetPreferredSizeShouldWorkBeforeShown(int width, int height, WindowStyle style) + { + GetPreferredSizeShouldWorkHelper(width, height, style, false); + } - [Test, ManualTest] - public void WindowFromPointShouldReturnWindowUnderPoint() + void GetPreferredSizeShouldWorkHelper(int width, int height, WindowStyle style, bool inLoadComplete) => Async(async () => + { + var padding = new Padding(10); + var child = new Panel { Size = new Size(width, height) }; + var parent = new TableLayout { - ManualTest("Move your mouse, it should show the title of the window under the mouse pointer", - form => - { - var content = new Panel { MinimumSize = new Size(100, 100) }; - var timer = new UITimer { Interval = 0.5 }; - timer.Elapsed += (sender, e) => - { - var window = Window.FromPoint(Mouse.Position); - content.Content = $"Window: {window?.Title}"; - }; - timer.Start(); - form.Closed += (sender, e) => - { - timer.Stop(); - }; - form.Title = "Test Form"; - return content; - } - ); - } - - [TestCase(200, 200, WindowStyle.Default)] - [TestCase(300, 200, WindowStyle.Default)] - [TestCase(200, 300, WindowStyle.Default)] - [TestCase(20, 20, WindowStyle.None)] - [TestCase(100, 100, WindowStyle.None)] - [TestCase(200, 200, WindowStyle.None)] - [TestCase(200, 100, WindowStyle.None)] - [TestCase(200, 100, WindowStyle.Utility)] - [TestCase(300, 200, WindowStyle.Utility)] - [TestCase(200, 300, WindowStyle.Utility)] - public void GetPreferredSizeShouldWorkInOnLoadComplete(int width, int height, WindowStyle style) + Rows = { + " ", + child + } + }; + var window = new T { - GetPreferredSizeShouldWorkHelper(width, height, style, true); - } - + Padding = padding, + WindowStyle = style, + // Resizable = false, + Content = parent + }; + bool? visibleAfterShown = null; + SizeF? preferredSize = null; + Size? shownSize = null; + Size? loadCompleteSize = null; + Size? shownChildSize = null; + Size? loadCompleteChildSize = null; + if (!inLoadComplete) + preferredSize = window.GetPreferredSize(); + + window.LoadComplete += (sender, e) => + { + loadCompleteSize = window.Size; + loadCompleteChildSize = child.Size; + if (inLoadComplete) + preferredSize = window.GetPreferredSize(); + }; + window.Shown += + async + (sender, e) => + { + shownSize = window.Size; + shownChildSize = child.Size; + await Task.Delay(100); + visibleAfterShown = window.Visible; + window.Close(); + }; + + // await Task.Delay(100); + + bool visibleBeforeShown = window.Visible; + + await ShowAsync(window); + + // Ensure content size is what was requested + Assert.That(shownChildSize, Is.Not.Null, "#1.1 Child Size should be set on Shown"); + Assert.That(shownChildSize.Value, Is.EqualTo(new Size(width, height)), "#1.2 Child Size does not match what was requested"); + + Assert.That(loadCompleteChildSize, Is.Not.Null, "#1.1 Child Size should be set on LoadComplete"); + Assert.That(loadCompleteChildSize.Value, Is.EqualTo(new Size(width, height)), "#1.2 Child Size should be set on LoadComplete"); + + Assert.NotNull(preferredSize, "#2.1 preferredSize not set (LoadComplete not called)"); + + // Preferred size will include window decorations + Assert.That(preferredSize.Value.Width, Is.GreaterThanOrEqualTo(width + padding.Horizontal), "#2.2 Preferred width is not in range"); + Assert.That(preferredSize.Value.Height, Is.GreaterThanOrEqualTo(height + padding.Vertical), "#2.3 Preferred height is not in range"); + + Assert.NotNull(shownSize, "#3.1 Actual size not set (Shown not called)"); + + Assert.That(shownSize.Value, Is.EqualTo(Size.Round(preferredSize.Value)), "#3.2 Shown size should match preferred size"); + + Assert.NotNull(loadCompleteSize, "#3.3 Size not set in LoadComplete"); + Assert.That(shownSize.Value, Is.EqualTo(loadCompleteSize.Value), "#3.4 Window size should be the same in both LoadComplete and Shown events"); + + Assert.NotNull(visibleAfterShown, "#4.1 VisibleWhenShown not set"); + Assert.IsTrue(visibleAfterShown, "#4.2 Window was not visible when shown"); + Assert.IsFalse(visibleBeforeShown, "#4.3 Visible should not be true before shown"); + }); + + [Test] + public void ShownShouldBeCalledInCorrectOrder() => Async(async () => + { + bool? parentShown = null; + bool? childShown = null; + bool? windowShown = null; + bool? parentShownBeforeOtherChild = null; + bool? windowShownBeforeChild = null; + + var child = new Label { Text = "Hello" }; + + child.Shown += (sender, e) => + { + childShown = true; + parentShownBeforeOtherChild = parentShown == true; + }; + + var parent = new TableLayout { Rows = { child } }; + parent.Shown += (sender, e) => + { + parentShown = true; + windowShownBeforeChild = windowShown == true; + }; + + var window = new T(); + window.Content = parent; + window.Shown += (sender, args) => + { + windowShown = true; + window.Close(); + }; + + await ShowAsync(window); + + Assert.NotNull(parentShown, "#1.1"); + Assert.NotNull(childShown, "#1.2"); + Assert.NotNull(windowShown, "#1.3"); + Assert.NotNull(parentShownBeforeOtherChild, "#1.4"); + Assert.NotNull(windowShownBeforeChild, "#1.5"); + + Assert.That(parentShown, Is.True, "#2.1 - Shown was not triggered for parent control"); + Assert.That(childShown, Is.True, "#2.2 - Shown was not triggered for child control"); + Assert.That(windowShown, Is.True, "#2.3 - Shown was not triggered for Window"); + + Assert.That(parentShownBeforeOtherChild, Is.True, "#3.1 - Parent should call Shown before its Child was called"); + Assert.That(windowShownBeforeChild, Is.False, "#3.2 - Window called Shown before Child was called"); + }); + + [Test] + [ManualTest] + public void WindowShouldNotBeShowingDuringLoadComplete() => Async(async () => + { + bool? loadComplete = null; + bool? shown = null; - [TestCase(200, 200, WindowStyle.Default)] - [TestCase(300, 200, WindowStyle.Default)] - [TestCase(200, 300, WindowStyle.Default)] - [TestCase(20, 20, WindowStyle.None)] - [TestCase(100, 100, WindowStyle.None)] - [TestCase(200, 200, WindowStyle.None)] - [TestCase(200, 100, WindowStyle.None)] - [TestCase(200, 100, WindowStyle.Utility)] - [TestCase(300, 200, WindowStyle.Utility)] - [TestCase(200, 300, WindowStyle.Utility)] - public void GetPreferredSizeShouldWorkBeforeShown(int width, int height, WindowStyle style) + var window = new T(); + window.Content = "Form should shown entriely\nwithout being blank first.\n\nClick to close."; + window.Width = 200; + window.LoadComplete += (sender, e) => { - GetPreferredSizeShouldWorkHelper(width, height, style, false); - } - - void GetPreferredSizeShouldWorkHelper(int width, int height, WindowStyle style, bool inLoadComplete) => Async(async () => + loadComplete = window.Visible; + Thread.Sleep(2000); + }; + window.MouseDown += (sender, e) => window.Close(); + window.Shown += (sender, e) => { - var padding = new Padding(10); - var child = new Panel { Size = new Size(width, height) }; - var parent = new TableLayout - { - Rows = { - " ", - child - } - }; - var window = new T - { - Padding = padding, - WindowStyle = style, - // Resizable = false, - Content = parent - }; - SizeF? preferredSize = null; - Size? shownSize = null; - Size? loadCompleteSize = null; - Size? shownChildSize = null; - Size? loadCompleteChildSize = null; - if (!inLoadComplete) - preferredSize = window.GetPreferredSize(); - - window.LoadComplete += (sender, e) => - { - loadCompleteSize = window.Size; - loadCompleteChildSize = child.Size; - if (inLoadComplete) - preferredSize = window.GetPreferredSize(); - }; - window.Shown += - // async - (sender, e) => - { - shownSize = window.Size; - shownChildSize = child.Size; - // await Task.Delay(1000); // if you want to see the results - window.Close(); - }; - - await ShowAsync(window); - - // Ensure content size is what was requested - Assert.That(shownChildSize, Is.Not.Null, "#1.1 Child Size should be set on Shown"); - Assert.That(shownChildSize.Value, Is.EqualTo(new Size(width, height)), "#1.2 Child Size does not match what was requested"); - - Assert.That(loadCompleteChildSize, Is.Not.Null, "#1.1 Child Size should be set on LoadComplete"); - Assert.That(loadCompleteChildSize.Value, Is.EqualTo(new Size(width, height)), "#1.2 Child Size should be set on LoadComplete"); - - Assert.NotNull(preferredSize, "#2.1 preferredSize not set (LoadComplete not called)"); - - // Preferred size will include window decorations - Assert.That(preferredSize.Value.Width, Is.GreaterThanOrEqualTo(width + padding.Horizontal), "#2.2 Preferred width is not in range"); - Assert.That(preferredSize.Value.Height, Is.GreaterThanOrEqualTo(height + padding.Vertical), "#2.3 Preferred height is not in range"); - - Assert.NotNull(shownSize, "#3.1 Actual size not set (Shown not called)"); - - Assert.That(shownSize.Value, Is.EqualTo(Size.Round(preferredSize.Value)), "#3.2 Shown size should match preferred size"); + shown = window.Visible; + }; - Assert.NotNull(loadCompleteSize, "#3.3 Size not set in LoadComplete"); - Assert.That(shownSize.Value, Is.EqualTo(loadCompleteSize.Value), "#3.4 Window size should be the same in both LoadComplete and Shown events"); - }); + await ShowAsync(window); - [Test] - public void ShownShouldBeCalledInCorrectOrder() => Async(async () => - { - bool? parentShown = null; - bool? childShown = null; - bool? windowShown = null; - bool? parentShownBeforeOtherChild = null; - bool? windowShownBeforeChild = null; - - var child = new Label { Text = "Hello" }; - - child.Shown += (sender, e) => - { - childShown = true; - parentShownBeforeOtherChild = parentShown == true; - }; - - var parent = new TableLayout { Rows = { child } }; - parent.Shown += (sender, e) => - { - parentShown = true; - windowShownBeforeChild = windowShown == true; - }; - - var window = new T(); - window.Content = parent; - window.Shown += (sender, args) => - { - windowShown = true; - window.Close(); - }; - - await ShowAsync(window); - - Assert.NotNull(parentShown, "#1.1"); - Assert.NotNull(childShown, "#1.2"); - Assert.NotNull(windowShown, "#1.3"); - Assert.NotNull(parentShownBeforeOtherChild, "#1.4"); - Assert.NotNull(windowShownBeforeChild, "#1.5"); - - Assert.That(parentShown, Is.True, "#2.1 - Shown was not triggered for parent control"); - Assert.That(childShown, Is.True, "#2.2 - Shown was not triggered for child control"); - Assert.That(windowShown, Is.True, "#2.3 - Shown was not triggered for Window"); - - Assert.That(parentShownBeforeOtherChild, Is.True, "#3.1 - Parent should call Shown before its Child was called"); - Assert.That(windowShownBeforeChild, Is.False, "#3.2 - Window called Shown before Child was called"); - }); - } + Assert.That(loadComplete, Is.True, "#1.1 - Window should be visible during LoadComplete"); + Assert.That(shown, Is.True, "#1.2 - Window should be visible during Shown"); + }); } -