diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 77bfa828a7..587cc98d32 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -26,6 +26,12 @@ + + + + TopmostWindowTest.axaml + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 55c96490d5..64c94ad9fa 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -170,6 +170,7 @@ + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 986eb920a3..4f033ea346 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -217,6 +217,15 @@ namespace IntegrationTestApp window.WindowState = WindowState.Normal; } } + + private void ShowTopmostWindow() + { + var mainWindow = new TopmostWindowTest("OwnerWindow") { Topmost = true, Title = "Owner Window"}; + var ownedWindow = new TopmostWindowTest("OwnedWindow") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Title = "Owned Window"}; + mainWindow.Show(); + + ownedWindow.Show(mainWindow); + } private void InitializeGesturesTab() { @@ -284,6 +293,8 @@ namespace IntegrationTestApp WindowState = WindowState.Normal; if (source?.Name == "RestoreAll") RestoreAll(); + if (source?.Name == "ShowTopmostWindow") + ShowTopmostWindow(); } } } diff --git a/samples/IntegrationTestApp/TopmostWindowTest.axaml b/samples/IntegrationTestApp/TopmostWindowTest.axaml new file mode 100644 index 0000000000..fbd2d9cf40 --- /dev/null +++ b/samples/IntegrationTestApp/TopmostWindowTest.axaml @@ -0,0 +1,17 @@ + + + + + + diff --git a/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs b/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs new file mode 100644 index 0000000000..4f2bec5237 --- /dev/null +++ b/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace IntegrationTestApp; + +public class TopmostWindowTest : Window +{ + public TopmostWindowTest(string name) + { + Name = name; + InitializeComponent(); + PositionChanged += (s, e) => this.GetControl("CurrentPosition").Text = $"{Position}"; + } + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void Button_OnClick(object? sender, RoutedEventArgs e) + { + Position += new PixelPoint(100, 100); + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index dfa3eff9b0..bf0d20ee79 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -708,6 +708,10 @@ namespace Avalonia.Win32 _hiddenWindowIsParent = parentHwnd == OffscreenParentWindow.Handle; SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, parentHwnd); + + // Windows doesn't seem to respect the HWND_TOPMOST flag of a window when showing an owned window for the first time. + // So we set the HWND_TOPMOST again before the owned window is shown. This only needs to be done once. + (parent as WindowImpl)?.EnsureTopmost(); } public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); @@ -860,6 +864,17 @@ namespace Avalonia.Win32 _topmost = value; } + private void EnsureTopmost() + { + if(_topmost) + { + SetWindowPos(_hwnd, + WindowPosZOrder.HWND_TOPMOST, + 0, 0, 0, 0, + SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE); + } + } + public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { _currentThemeVariant = themeVariant; diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 761fa0ee67..382af64e51 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -257,6 +257,35 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(new Rgba32(255, 0, 0), centerColor); } + [PlatformFact(TestPlatforms.Windows)] + public void Owned_Window_Should_Appear_Above_Topmost_Owner() + { + var showTopmostWindow = _session.FindElementByAccessibilityId("ShowTopmostWindow"); + using var window = showTopmostWindow.OpenWindowWithClick(); + Thread.Sleep(1000); + var ownerWindow = GetWindow("OwnerWindow"); + var ownedWindow = GetWindow("OwnedWindow"); + + Assert.NotNull(ownerWindow); + Assert.NotNull(ownedWindow); + + var ownerPosition = GetPosition(ownerWindow); + var ownedPosition = GetPosition(ownedWindow); + + // Owned Window moves + var moveButton = ownedWindow.FindElementByAccessibilityId("MoveButton"); + moveButton.Click(); + Thread.Sleep(1000); + + Assert.Equal(GetPosition(ownerWindow), ownerPosition); + Assert.NotEqual(GetPosition(ownedWindow), ownedPosition); + + PixelPoint GetPosition(AppiumWebElement window) + { + return PixelPoint.Parse(window.FindElementByAccessibilityId("CurrentPosition").Text); + } + } + [Theory] [InlineData(ShowWindowMode.NonOwned, true)] [InlineData(ShowWindowMode.Owned, true)]