Просмотр исходного кода

Win32 - Ensure owner topmost flag is set if its topmost when showing a owned window (#16104)

* ensure owner topmost flag is set if its topmost when showing a owned window

* add comments on why HWND_TOPMOST is set again

* add Topmost with owned window integration tests

* fix tests
Emmanuel Hansen 1 год назад
Родитель
Сommit
fc26fb6608

+ 6 - 0
samples/IntegrationTestApp/IntegrationTestApp.csproj

@@ -26,6 +26,12 @@
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
   </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="TopmostWindowTest.axaml.cs">
+      <DependentUpon>TopmostWindowTest.axaml</DependentUpon>
+    </Compile>
+  </ItemGroup>
   
   <Import Project="..\..\build\BuildTargets.targets" />
   <Import Project="..\..\build\SampleApp.props" />

+ 1 - 0
samples/IntegrationTestApp/MainWindow.axaml

@@ -170,6 +170,7 @@
             <Button Name="EnterFullscreen">Enter Fullscreen</Button>
             <Button Name="ExitFullscreen">Exit Fullscreen</Button>
             <Button Name="RestoreAll">Restore All</Button>
+            <Button Name="ShowTopmostWindow">Show Topmost Window</Button>
           </StackPanel>
           <StackPanel Grid.Column="2">
             <Button Name="ShowTransparentWindow">Transparent Window</Button>

+ 11 - 0
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();
         }
     }
 }

+ 17 - 0
samples/IntegrationTestApp/TopmostWindowTest.axaml

@@ -0,0 +1,17 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="IntegrationTestApp.TopmostWindowTest"
+        Title="TopmostWindowTest"
+        Width="640"
+        Height="480">
+  <Grid>
+    <TextBox Name="CurrentPosition"
+             Grid.Column="1"
+             Grid.Row="3"
+             IsReadOnly="True" />
+    <Button HorizontalAlignment="Center" Name="MoveButton" VerticalAlignment="Center" Click="Button_OnClick">Move</Button>
+  </Grid>
+</Window>

+ 25 - 0
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<TextBox>("CurrentPosition").Text = $"{Position}";
+    }
+    private void InitializeComponent()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+
+    private void Button_OnClick(object? sender, RoutedEventArgs e)
+    {
+        Position += new PixelPoint(100, 100);
+    }
+}

+ 15 - 0
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;

+ 29 - 0
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)]