从最大化恢复时的 WPF 窗口状态卡在奇数状态 [英] WPF window state when restored from maximized gets stuck in odd state

查看:29
本文介绍了从最大化恢复时的 WPF 窗口状态卡在奇数状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm seeing some strange behaviour from WPF. I have a form with three buttons on it. One button should make the window fullscreen, one should center it on the monitor its currently on, the third button should restore the window to its normal position.

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

and the code is below. The strange behaviour is that when I maximize, and then restore the window, the position is correctly restored but the window still thinks it's maximized (the maximize button looks like a restore button and you can't resize the window even though ResizeMode has been set to CanResizeWithGrip).

当最大化的窗口恢复后,即使窗口位置没有最大化,它仍然认为它仍然最大化,只需通过拖动标题栏手动移动窗口就足以使其自我纠正回非最大化模式.

When the maximized window has been restored, and it thinks its still maximized even though the window position isn't maximized, just moving the window manually by dragging the title bar is enough to cause it to correct itself back to non-maximized mode.

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

Also, if I maximize then restore the window and then maximize it again without moving it, the maximized window position is incorrect (not in the top left).

谜团越来越深.如果我最大化然后恢复窗口,然后按 alt,然后按下(以获取窗口菜单)并选择移动",然后用键盘移动窗口,它仍然停留在虚假未最大化模式"中,即使窗口正在移动,所以似乎唯一的方法是用鼠标移动它.

And the mystery deepens. If I maximize then restore the window, then press alt, then press down (to get the window menu) and select 'Move' and then move the window around with the keyboard, it stays stuck in 'bogus not-mazimized mode' even though the window is being moved, so it seems the only way to unstick it is to move it with the mouse.

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);
        }
    }
}

推荐答案

我没有完整的答案给你.但是,您会发现删除 Hide() 和 Show() 调用后,您的代码开始工作得更好.

I don't have a complete answer for you. However you will find that your code starts working a lot better once you remove the Hide() and Show() calls.

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

我确定您将其放入以减少闪烁,但我认为正在发生的是 Hide() 和 Show() 调用正在翻转底层操作系统窗口的窗口样式字中的 WS_VISIBLE 位,这是包含 WS_MAXIMIZE 和 WS_BORDER 以及您正在操纵的其他一些东西的同一个词.请参阅 https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx

I'm sure you put this in to reduce flicker, but what I think is happening is that the Hide() and Show() calls are flipping the WS_VISIBLE bit in the window style word of the underlying OS window which is the same word which contains the WS_MAXIMIZE and WS_BORDER and some other things that you are manipulating. See https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx

需要更多的研究才能弄清楚到底发生了什么,但我认为根本问题是抽象泄漏".您的代码将 top、left、style 和 state 设置为独立的非耦合变量.但他们不是!要设置左,必须调用 OS SetWindowPos() 函数,该函数不需要左上角坐标、窗口大小、Z 顺序以及可见性标志以及窗口是否最大化!请参阅 https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx.因此,每次设置这些独立"变量之一时,您都是在敲击 SetWindowPos().这个 API 调用让人回想起过去的糟糕时光,那时 CPU 周期非常宝贵,您需要将尽可能多的功能打包到每个 API 调用中.

It would take more research to figure out what is exactly going on, but the fundamental problem I believe is a "leaky abstraction". Your code sets top, left, style and state as if these were independent uncoupled variables. But they are not! To set left, the OS SetWindowPos() function must be called which requires not the upper left coordinate, the window size, the Z order as well as visibility flags and whether the windows is maximized! See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx. So each time you set one of these "independent" variables you are pounding SetWindowPos(). This API call harks back to the bad old days when CPU cycles were precious and you need to pack as much functionality as possible into each API call.

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

Ironically this is making your code very inefficient. I think the thing to do to straighten this out is to bypass the leaking abstraction of System.Windows.Window and call SetWindowPos and possibility other API functions directly from user32.dll. Then things will be a lot more predicable.

这篇关于从最大化恢复时的 WPF 窗口状态卡在奇数状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆