从最大化恢复时的WPF窗口状态陷入奇怪状态

时间:2017-07-25 16:32:45

标签: wpf window

我从WPF看到了一些奇怪的行为。我有一个带有三个按钮的表格。一个按钮应该使窗口全屏,一个应该将它放在显示器当前打开的位置,第三个按钮应该将窗口恢复到正常位置。

XAML

<Window x:Class="TestRestore.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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"
        xmlns:local="clr-namespace:TestRestore"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen">
    <Grid>
        <Button Content="Max" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="94" Click="max_click" Name="max_button"/>
        <Button Content="Center" HorizontalAlignment="Left" Margin="10,35,0,0" VerticalAlignment="Top" Width="94" Click="center_click" Name="center_button"/>
        <Button Content="Restore" HorizontalAlignment="Left" Margin="227,143,0,0" VerticalAlignment="Top" Width="75" Click="restore_click" Name="restore_button" IsEnabled="False"/>
    </Grid>
</Window>

,代码如下。奇怪的行为是,当我最大化,然后恢复窗口时,位置被正确恢复,但窗口仍然认为它已经最大化(最大化按钮看起来像一个恢复按钮,你不能调整窗口大小即使ResizeMode已设置为CanResizeWithGrip)。

当最大化窗口已经恢复时,即使窗口位置没有最大化,它仍认为它仍然最大化,只需通过拖动标题栏手动移动窗口就足以使其自身恢复为非 - 最大化模式。

此外,如果我最大化然后恢复窗口然后再次最大化它而不移动它,最大化的窗口位置是不正确的(不在左上角)。

这个谜团加深了。如果我最大化然后恢复窗口,然后按alt,然后按下(以获取窗口菜单)并选择&#39; Move&#39;然后用键盘移动窗口,它仍然停留在“伪造的模式”模式中。即使窗口正在被移动,所以看起来解开它的唯一方法就是用鼠标移动它。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace TestRestore
{
    public partial class MainWindow : Window
    {
        WindowStyle old_window_style;
        WindowState old_window_state;
        double old_left;
        double old_top;
        double old_width;
        double old_height;

        public MainWindow()
        {
            InitializeComponent();
        }

        // remember position, style and state
        private void SaveWindowPos()
        {
            old_window_style = WindowStyle;
            old_window_state = WindowState;
            old_left = Left;
            old_top = Top;
            old_width = Width;
            old_height = Height;
            max_button.IsEnabled = false;
            center_button.IsEnabled = false;
            restore_button.IsEnabled = true;
        }

        // put position, style and state back
        private void RestoreWindowPos()
        {
            WindowStyle = old_window_style;
            WindowState = old_window_state;
            ResizeMode = ResizeMode.CanResizeWithGrip;
            Left = old_left;
            Top = old_top;
            Width = old_width;
            Height = old_height;
            max_button.IsEnabled = true;
            center_button.IsEnabled = true;
            restore_button.IsEnabled = false;
        }

        // make it centered or fullscreen
        private void SetActivePos(bool full_screen)
        {
            SaveWindowPos();
            Hide();
            if (full_screen)
            {
                ResizeMode = ResizeMode.NoResize;
                WindowStyle = WindowStyle.None;
                WindowState = WindowState.Maximized;
            }
            else
            {
                Size s = new Size(800, 600);
                Point p = CenterRectInMonitor(this, s);
                Left = p.X;
                Top = p.Y;
                Width = s.Width;
                Height = s.Height;
                ResizeMode = ResizeMode.NoResize;
                WindowState = WindowState.Normal;
            }
            Show();
        }

        private void restore_click(object sender, RoutedEventArgs e)
        {
            Hide();
            RestoreWindowPos();
            Show();
        }

        private void max_click(object sender, RoutedEventArgs e)
        {
            SetActivePos(true);
        }

        private void center_click(object sender, RoutedEventArgs e)
        {
            SetActivePos(false);
        }

        // interop

        public const Int32 MONITOR_DEFAULTTOPRIMARY = 0x00000001;
        public const Int32 MONITOR_DEFAULTTONEAREST = 0x00000002;

        [DllImport("user32.dll")]
        public static extern IntPtr MonitorFromWindow(IntPtr handle, Int32 flags);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi);

        // size of a device name string
        private const int CCHDEVICENAME = 32;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct MonitorInfoEx
        {
            public int Size;
            public RectStruct Monitor;
            public RectStruct WorkArea;
            public uint Flags;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
            public string DeviceName;

            public void Init()
            {
                this.Size = 40 + 2 * CCHDEVICENAME;
                this.DeviceName = string.Empty;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RectStruct
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;

            public int Width
            {
                get
                {
                    return Right - Left;
                }
            }

            public int Height
            {
                get
                {
                    return Bottom - Top;
                }
            }
        }

        public static MonitorInfoEx GetMonitorFromWindow(Window w)
        {
            var hwnd = new WindowInteropHelper(w).EnsureHandle();
            var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
            MonitorInfoEx monitor_info = new MonitorInfoEx();
            monitor_info.Init();
            GetMonitorInfo(monitor, ref monitor_info);
            return monitor_info;
        }

        // work out how a rect of 'Size size' should be centered on the monitor containing 'Window w'
        public static Point CenterRectInMonitor(Window w, Size size)
        {
            var source = PresentationSource.FromVisual(w);
            double x_scale = source.CompositionTarget.TransformToDevice.M11;
            double y_scale = source.CompositionTarget.TransformToDevice.M22;
            var width = size.Width * x_scale;
            var height = size.Height * y_scale;
            var monitor_info = GetMonitorFromWindow(w);
            Size s = new Size(monitor_info.Monitor.Width, monitor_info.Monitor.Height);
            Point p = new Point(monitor_info.Monitor.Left, monitor_info.Monitor.Top);
            Point c = new Point(p.X + s.Width / 2, p.Y + s.Height / 2);
            return new Point((c.X - width / 2) / x_scale, (c.Y - height / 2) / y_scale);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我没有完整的答案。但是,一旦删除Hide()和Show()调用,您会发现代码开始运行得更好。

    private void restore_click(object sender, RoutedEventArgs e)
    {
//            Hide();
        RestoreWindowPos();
//            Show();
    }

我确定你把它放入以减少闪烁,但我认为正在发生的是Hide()和Show()调用正在翻转底层OS窗口的窗口样式字中的WS_VISIBLE位,即包含WS_MAXIMIZE和WS_BORDER的相同单词以及您正在操作的其他一些内容。见https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx

需要更多的研究来弄清楚究竟发生了什么,但我认为根本问题是“漏洞抽象”。您的代码将top,left,style和state设置为独立的非耦合变量。但他们不是!要设置为left,必须调用OS SetWindowPos()函数,该函数不需要左上角坐标,窗口大小,Z顺序以及可见性标志以及窗口是否最大化!见https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx。因此,每次设置其中一个“独立”变量时,您都会碰到SetWindowPos()。这个API调用可以追溯到CPU周期非常宝贵的旧时代,您需要在每个API调用中尽可能多地打包功能。

具有讽刺意味的是,这会使您的代码效率低下。我认为要解决这个问题的方法是绕过System.Windows.Window的泄漏抽象,并直接从user32.dll调用SetWindowPos和其他API函数。然后事情会更加可预测。