From 7f411a9b5dbe1d4e63b0c0be8cc37ae368036feb Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Wed, 2 Feb 2022 14:51:00 -0800 Subject: [PATCH 1/2] Wpf: Fix more window active issues. - Fix closing a form with the 'x' vs. just programmatically. - Fix window focus issues with Dialog if the owner does not have focus it'll mess up its z-order when closed. Implement `HwndFormHandler.Focus()` and `HasFocus` Relates to #2131 --- src/Eto.WinForms/Forms/HwndFormHandler.cs | 23 +--- src/Eto.WinForms/Win32.cs | 6 + src/Eto.Wpf/Forms/DialogHandler.cs | 133 ++++++++++--------- src/Eto.Wpf/Forms/FormHandler.cs | 3 +- src/Eto.Wpf/Forms/WpfWindow.cs | 10 +- test/Eto.Test/UnitTests/Forms/DialogTests.cs | 40 ++++++ 6 files changed, 127 insertions(+), 88 deletions(-) diff --git a/src/Eto.WinForms/Forms/HwndFormHandler.cs b/src/Eto.WinForms/Forms/HwndFormHandler.cs index bec08f29cb..681c18cf18 100644 --- a/src/Eto.WinForms/Forms/HwndFormHandler.cs +++ b/src/Eto.WinForms/Forms/HwndFormHandler.cs @@ -442,35 +442,20 @@ public void Invalidate(Eto.Drawing.Rectangle rect, bool invalidateChildren) public void SuspendLayout() { - throw new NotImplementedException(); } public void ResumeLayout() { - throw new NotImplementedException(); } - public void Focus() - { - throw new NotImplementedException(); - } + public void Focus() => Win32.SetActiveWindow(Control); - public bool HasFocus - { - get { throw new NotImplementedException(); } - } + public bool HasFocus => Win32.GetActiveWindow() == Control; public bool Visible { - get - { - return Win32.IsWindowVisible(Control); - } - set - { - Win32.ShowWindow(Control, value ? Win32.SW.SHOWNA : Win32.SW.HIDE); - } - + get => Win32.IsWindowVisible(Control); + set => Win32.ShowWindow(Control, value ? Win32.SW.SHOWNA : Win32.SW.HIDE); } public void OnPreLoad(EventArgs e) diff --git a/src/Eto.WinForms/Win32.cs b/src/Eto.WinForms/Win32.cs index 9207f8a52f..14ee4b34e5 100644 --- a/src/Eto.WinForms/Win32.cs +++ b/src/Eto.WinForms/Win32.cs @@ -285,6 +285,12 @@ public static MouseButtons GetMouseButtonWParam(IntPtr wParam) [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ShowWindow(IntPtr hWnd, SW nCmdShow); + + [DllImport("user32.dll")] + public static extern IntPtr GetActiveWindow(); + + [DllImport("user32.dll")] + public static extern IntPtr SetActiveWindow(IntPtr hWnd); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/src/Eto.Wpf/Forms/DialogHandler.cs b/src/Eto.Wpf/Forms/DialogHandler.cs index b51a65167d..b4caa31a01 100644 --- a/src/Eto.Wpf/Forms/DialogHandler.cs +++ b/src/Eto.Wpf/Forms/DialogHandler.cs @@ -12,8 +12,8 @@ public class DialogHandler : WpfWindow, Dia { Button defaultButton; Rectangle? parentWindowBounds; - swc.DockPanel dockMain; - swc.Grid gridButtons; + swc.DockPanel dockMain; + swc.Grid gridButtons; public DialogHandler() : this(new sw.Window()) { } @@ -34,19 +34,19 @@ public DialogHandler(sw.Window window) gridButtons.Margin = new sw.Thickness(); } - public override void SetContainerContent(sw.FrameworkElement content) - { - this.content.Children.Add(dockMain); - swc.DockPanel.SetDock(gridButtons, swc.Dock.Bottom); - dockMain.Children.Add(gridButtons); - dockMain.Children.Add(content); - } + public override void SetContainerContent(sw.FrameworkElement content) + { + this.content.Children.Add(dockMain); + swc.DockPanel.SetDock(gridButtons, swc.Dock.Bottom); + dockMain.Children.Add(gridButtons); + dockMain.Children.Add(content); + } - public DialogDisplayMode DisplayMode { get; set; } + public DialogDisplayMode DisplayMode { get; set; } public void ShowModal() { - ReloadButtons(); + ReloadButtons(); if (LocationSet) { @@ -59,10 +59,13 @@ public void ShowModal() parentWindowBounds = Widget.Owner.Bounds; Control.Loaded += HandleLoaded; } + // if the owner doesn't have focus, WPF changes the owner's z-order after the dialog closes. + if (!Widget.Owner.HasFocus) + Widget.Owner?.Focus(); Control.ShowDialog(); WpfFrameworkElementHelper.ShouldCaptureMouse = false; - ClearButtons(); + ClearButtons(); } void Control_PreviewKeyDown(object sender, sw.Input.KeyEventArgs e) @@ -99,59 +102,59 @@ void HandleLoaded(object sender, EventArgs e) } private void ClearButtons() - { - gridButtons.ColumnDefinitions.Clear(); - gridButtons.Children.Clear(); - } - - private void ReloadButtons() - { - gridButtons.ColumnDefinitions.Add(new swc.ColumnDefinition { Width = new sw.GridLength(100, sw.GridUnitType.Star) }); - - var negativeButtons = Widget.NegativeButtons; - var positiveButtons = Widget.PositiveButtons; - var hasButtons = negativeButtons.Count + positiveButtons.Count > 0; - - for (int i = positiveButtons.Count - 1; i >= 0; i--) - AddButton(positiveButtons.Count - i, positiveButtons[i]); - - for (int i = 0;i < negativeButtons.Count;i++) - AddButton(positiveButtons.Count + 1 + i, negativeButtons[i]); - - gridButtons.Visibility = hasButtons ? System.Windows.Visibility.Visible : System.Windows.Visibility.Hidden; - gridButtons.Margin = new sw.Thickness(hasButtons ? 8 : 0); - } - - private void AddButton(int pos, Button button) - { - var native = button.ToNative(); - native.Margin = new sw.Thickness(6, 0, 0, 0); - - swc.Grid.SetColumn(native, pos); - - gridButtons.ColumnDefinitions.Add(new swc.ColumnDefinition { Width = new sw.GridLength(1, sw.GridUnitType.Auto) }); - gridButtons.Children.Add(native); - } - - public void InsertDialogButton(bool positive, int index, Button item) - { - if(Widget.Visible) - { - ClearButtons(); - ReloadButtons(); - } - } - - public void RemoveDialogButton(bool positive, int index, Button item) - { - if (Widget.Visible) - { - ClearButtons(); - ReloadButtons(); - } - } - - public Button DefaultButton + { + gridButtons.ColumnDefinitions.Clear(); + gridButtons.Children.Clear(); + } + + private void ReloadButtons() + { + gridButtons.ColumnDefinitions.Add(new swc.ColumnDefinition { Width = new sw.GridLength(100, sw.GridUnitType.Star) }); + + var negativeButtons = Widget.NegativeButtons; + var positiveButtons = Widget.PositiveButtons; + var hasButtons = negativeButtons.Count + positiveButtons.Count > 0; + + for (int i = positiveButtons.Count - 1; i >= 0; i--) + AddButton(positiveButtons.Count - i, positiveButtons[i]); + + for (int i = 0; i < negativeButtons.Count; i++) + AddButton(positiveButtons.Count + 1 + i, negativeButtons[i]); + + gridButtons.Visibility = hasButtons ? System.Windows.Visibility.Visible : System.Windows.Visibility.Hidden; + gridButtons.Margin = new sw.Thickness(hasButtons ? 8 : 0); + } + + private void AddButton(int pos, Button button) + { + var native = button.ToNative(); + native.Margin = new sw.Thickness(6, 0, 0, 0); + + swc.Grid.SetColumn(native, pos); + + gridButtons.ColumnDefinitions.Add(new swc.ColumnDefinition { Width = new sw.GridLength(1, sw.GridUnitType.Auto) }); + gridButtons.Children.Add(native); + } + + public void InsertDialogButton(bool positive, int index, Button item) + { + if (Widget.Visible) + { + ClearButtons(); + ReloadButtons(); + } + } + + public void RemoveDialogButton(bool positive, int index, Button item) + { + if (Widget.Visible) + { + ClearButtons(); + ReloadButtons(); + } + } + + public Button DefaultButton { get { return defaultButton; } set diff --git a/src/Eto.Wpf/Forms/FormHandler.cs b/src/Eto.Wpf/Forms/FormHandler.cs index 649df0fee3..514de25c3c 100644 --- a/src/Eto.Wpf/Forms/FormHandler.cs +++ b/src/Eto.Wpf/Forms/FormHandler.cs @@ -56,11 +56,10 @@ public virtual void Show() WpfFrameworkElementHelper.ShouldCaptureMouse = false; } - protected override void InternalClose() + protected override void InternalClosing() { // Clear owner so WPF doesn't change the z-order of the parent when closing SetOwner(null); - Control.Close(); } public bool ShowActivated diff --git a/src/Eto.Wpf/Forms/WpfWindow.cs b/src/Eto.Wpf/Forms/WpfWindow.cs index 5d41930cc1..ba9a7b00fd 100644 --- a/src/Eto.Wpf/Forms/WpfWindow.cs +++ b/src/Eto.Wpf/Forms/WpfWindow.cs @@ -281,6 +281,10 @@ private void Control_Closing(object sender, CancelEventArgs e) e.Cancel = args.Cancel; IsApplicationClosing = !e.Cancel && willShutDown; IsClosing = !e.Cancel; + if (!e.Cancel) + { + InternalClosing(); + } } float LastPixelSize @@ -403,7 +407,9 @@ public Eto.Forms.ToolBar ToolBar } } - protected virtual void InternalClose() => Control.Close(); + protected virtual void InternalClosing() + { + } public void Close() { @@ -412,7 +418,7 @@ public void Close() // prevent crash if we call this more than once.. if (!IsClosing) { - InternalClose(); + Control.Close(); } } else diff --git a/test/Eto.Test/UnitTests/Forms/DialogTests.cs b/test/Eto.Test/UnitTests/Forms/DialogTests.cs index 3f17d9024a..399956e048 100644 --- a/test/Eto.Test/UnitTests/Forms/DialogTests.cs +++ b/test/Eto.Test/UnitTests/Forms/DialogTests.cs @@ -176,5 +176,45 @@ public void MultipleDialogsShouldAllowClosingInDifferentOrders(bool inOrder, boo Assert.IsTrue(firstWasClosedFirst, "Modal 1 did not close before Modal 2"); } + [Test] + [ManualTest] + public void DialogShouldCloseWithoutHidingParent() + { + ManualForm("Click on the child window to open another child.\nClosing by clicking or hitting the 'x' should\nnot make any windows go behind other applications.", + form => + { + var content = new Panel { MinimumSize = new Size(100, 100) }; + form.Shown += (sender, e) => + { + // this form is used so the parent of the dialog won't have focus when it is shown + var someOtherActivatingForm = new Form + { + ClientSize = new Size(100, 100), + Content = TableLayout.AutoSized("Click Me", centered: true) + }; + someOtherActivatingForm.LostFocus += (s2, e2) => someOtherActivatingForm.Close(); + someOtherActivatingForm.MouseDown += (s3, e3) => { + + var childForm = new Dialog + { + Title = "Child Dialog", + ClientSize = new Size(100, 100), + Owner = form, + Content = TableLayout.AutoSized("Click Me too", centered: true) + }; + childForm.MouseDown += (s2, e2) => childForm.Close(); + + // form doesn't have focus here (yet) + childForm.ShowModal(); + }; + someOtherActivatingForm.Show(); + }; + form.Title = "Test Form"; + form.Owner = Application.Instance.MainForm; + return content; + } + ); + } + } } From 784cd0ae71b994b46ec4c0d0ee4c341f54acaa78 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Wed, 2 Feb 2022 15:13:05 -0800 Subject: [PATCH 2/2] WinForms implementation for window focus fixes --- src/Eto.WinForms/Forms/DialogHandler.cs | 3 ++ src/Eto.WinForms/Forms/FormHandler.cs | 6 +++ src/Eto.WinForms/Forms/WindowHandler.cs | 61 +++++++++++++++++-------- src/Eto.Wpf/Forms/DialogHandler.cs | 14 ++++-- 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/Eto.WinForms/Forms/DialogHandler.cs b/src/Eto.WinForms/Forms/DialogHandler.cs index 30771dfe29..2f536ad78a 100644 --- a/src/Eto.WinForms/Forms/DialogHandler.cs +++ b/src/Eto.WinForms/Forms/DialogHandler.cs @@ -79,6 +79,9 @@ public Button DefaultButton public void ShowModal() { ReloadButtons(); + var owner = Widget.Owner; + if (owner != null && !owner.HasFocus) + owner.Focus(); Control.ShowDialog(); Control.Owner = null; // without this, the dialog is still active as part of the owner form diff --git a/src/Eto.WinForms/Forms/FormHandler.cs b/src/Eto.WinForms/Forms/FormHandler.cs index 1f7b383cd8..be31d1b03f 100644 --- a/src/Eto.WinForms/Forms/FormHandler.cs +++ b/src/Eto.WinForms/Forms/FormHandler.cs @@ -138,6 +138,12 @@ protected override void Initialize() Resizable = true; } + internal override void InternalClosing() + { + base.InternalClosing(); + SetOwner(null); + } + public void Show() { Control.Show(); diff --git a/src/Eto.WinForms/Forms/WindowHandler.cs b/src/Eto.WinForms/Forms/WindowHandler.cs index 50ea3c5732..bc21584a65 100755 --- a/src/Eto.WinForms/Forms/WindowHandler.cs +++ b/src/Eto.WinForms/Forms/WindowHandler.cs @@ -19,6 +19,7 @@ public interface IWindowHandler public class WindowHandler : Window.IWindowHandler { internal static readonly object MovableByWindowBackground_Key = new object(); + internal static readonly object IsClosing_Key = new object(); public Window FromPoint(PointF point) { @@ -246,21 +247,7 @@ public override void AttachEvent(string id) Control.FormClosed += (sender, e) => Callback.OnClosed(Widget, EventArgs.Empty); break; case Window.ClosingEvent: - Control.FormClosing += delegate(object sender, swf.FormClosingEventArgs e) - { - var args = new CancelEventArgs(e.Cancel); - Callback.OnClosing(Widget, args); - - if (!e.Cancel && swf.Application.OpenForms.Count <= 1 - || e.CloseReason == swf.CloseReason.ApplicationExitCall - || e.CloseReason == swf.CloseReason.WindowsShutDown) - { - var app = ((ApplicationHandler)Application.Instance.Handler); - app.Callback.OnTerminating(app.Widget, args); - } - - e.Cancel = args.Cancel; - }; + Control.FormClosing += Control_FormClosing; break; case Eto.Forms.Control.ShownEvent: Control.Shown += delegate @@ -275,10 +262,7 @@ public override void AttachEvent(string id) }; break; case Eto.Forms.Control.LostFocusEvent: - Control.Deactivate += delegate - { - Callback.OnLostFocus(Widget, EventArgs.Empty); - }; + Control.Deactivate += (sender, e) => Application.Instance.AsyncInvoke(() => Callback.OnLostFocus(Widget, EventArgs.Empty)); break; case Window.WindowStateChangedEvent: var oldState = Control.WindowState; @@ -300,6 +284,39 @@ public override void AttachEvent(string id) } } + bool IsClosing + { + get => Widget.Properties.Get(WindowHandler.IsClosing_Key); + set => Widget.Properties.Set(WindowHandler.IsClosing_Key, value); + } + + private void Control_FormClosing(object sender, swf.FormClosingEventArgs e) + { + IsClosing = true; + var args = new CancelEventArgs(e.Cancel); + Callback.OnClosing(Widget, args); + + if (!e.Cancel && swf.Application.OpenForms.Count <= 1 + || e.CloseReason == swf.CloseReason.ApplicationExitCall + || e.CloseReason == swf.CloseReason.WindowsShutDown) + { + var app = ((ApplicationHandler)Application.Instance.Handler); + app.Callback.OnTerminating(app.Widget, args); + } + + e.Cancel = args.Cancel; + IsClosing = !e.Cancel; + + if (!e.Cancel) + { + InternalClosing(); + } + } + + internal virtual void InternalClosing() + { + } + public MenuBar Menu { get @@ -389,7 +406,11 @@ public Eto.Forms.ToolBar ToolBar } } - public void Close() => Control.Close(); + public void Close() + { + if (!IsClosing) + Control.Close(); + } public Icon Icon { diff --git a/src/Eto.Wpf/Forms/DialogHandler.cs b/src/Eto.Wpf/Forms/DialogHandler.cs index b4caa31a01..8aa546a9fd 100644 --- a/src/Eto.Wpf/Forms/DialogHandler.cs +++ b/src/Eto.Wpf/Forms/DialogHandler.cs @@ -48,20 +48,24 @@ public void ShowModal() { ReloadButtons(); + var owner = Widget.Owner; + if (LocationSet) { Control.WindowStartupLocation = sw.WindowStartupLocation.Manual; } - else if (Widget.Owner != null) + else if (owner != null) { // CenterOwner does not work in certain cases (e.g. with autosizing) Control.WindowStartupLocation = sw.WindowStartupLocation.Manual; - parentWindowBounds = Widget.Owner.Bounds; + parentWindowBounds = owner.Bounds; Control.Loaded += HandleLoaded; } - // if the owner doesn't have focus, WPF changes the owner's z-order after the dialog closes. - if (!Widget.Owner.HasFocus) - Widget.Owner?.Focus(); + + // if the owner doesn't have focus, windows changes the owner's z-order after the dialog closes. + if (owner != null && !owner.HasFocus) + owner.Focus(); + Control.ShowDialog(); WpfFrameworkElementHelper.ShouldCaptureMouse = false;