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)]