【问题标题】:Maximized WPF window is approximately 8 pixels too large on each side最大化的 WPF 窗口每边大约 8 个像素太大
【发布时间】:2019-06-10 10:38:52
【问题描述】:

我正在尝试通过最大化我的 WindowChrome 窗口来解决以下问题:

  1. 最大化的窗口每边延伸到屏幕外大约 8 像素。
  2. Aero peek 在顶部和左侧显示了一个大约 8 像素的透明区域。
  3. 自动隐藏任务栏不适用于最大化窗口。

我找到了一个great article,它解释了如何解决任务栏问题。顺便说一句,使用此解决方案时,Aero peek、自动隐藏任务栏和最大化窗口都可以工作,但仅当任务栏设置为自动隐藏时。如果未设置为自动隐藏,则代码仅返回标准 MINMAXINFO。

奇怪的是,标准的 MINMAXINFO 似乎在最大化时应该导致窗口的正确位置和大小。我知道这一点,因为如果我只是从 MINMAXINFO 位置或最大尺寸的 x 值中减去 1 个像素,则最大化的窗口位于正确的位置,但它的宽度正好是 1 个像素太小。

但是,如果我不考虑标准的 MINMAXINFO,则窗口太大,远不止一个像素。就好像从 ptMaxPosition 或 ptMaxSize.x 中添加或减去任何东西都会导致窗口使用自定义的 MINMAXINFO。我试过加零和减零,但不幸的是没有用。

Here is a video 显示问题并希望有助于澄清问题。

这是我正在使用的代码:

    public MainWindow()
    {
        SourceInitialized += new EventHandler(Window1_SourceInitialized); 
        InitializeComponent();
    }

    void Window1_SourceInitialized(object sender, EventArgs e)
    {
        WindowSizing.WindowInitialized(this);
    }

    public static class WindowSizing
    {
        const int MONITOR_DEFAULTTONEAREST = 0x00000002;

        #region DLLImports
        [DllImport("shell32", CallingConvention = CallingConvention.StdCall)]
        public static extern int SHAppBarMessage(int dwMessage, ref APPBARDATA pData);

        [DllImport("user32", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32")]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        [DllImport("user32")]
        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
        #endregion

        private static MINMAXINFO AdjustWorkingAreaForAutoHide(IntPtr monitorContainingApplication, MINMAXINFO mmi)
        {
            IntPtr hwnd = FindWindow("Shell_TrayWnd", null);

            if (hwnd == null)
            {
                return mmi;
            }

            IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (!monitorContainingApplication.Equals(monitorWithTaskbarOnIt))
            {
                return mmi;
            }

            APPBARDATA abd = new APPBARDATA();

            abd.cbSize = Marshal.SizeOf(abd);

            abd.hWnd = hwnd;

            SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref abd);

            int uEdge = GetEdge(abd.rc);

            bool autoHide = Convert.ToBoolean(SHAppBarMessage((int)ABMsg.ABM_GETSTATE, ref abd));

            if (!autoHide)
            {
                return mmi;
            }

            switch (uEdge)
            {
                case (int)ABEdge.ABE_LEFT:
                    mmi.ptMaxPosition.x += 2;
                    mmi.ptMaxTrackSize.x -= 2;
                    mmi.ptMaxSize.x -= 2;
                    break;
                case (int)ABEdge.ABE_RIGHT:
                    mmi.ptMaxSize.x -= 2;
                    mmi.ptMaxTrackSize.x -= 2;
                    break;
                case (int)ABEdge.ABE_TOP:
                    mmi.ptMaxPosition.y += 2;
                    mmi.ptMaxTrackSize.y -= 2;
                    mmi.ptMaxSize.y -= 2;
                    break;
                case (int)ABEdge.ABE_BOTTOM:
                    mmi.ptMaxSize.y -= 2;
                    mmi.ptMaxTrackSize.y -= 2;
                    break;
                default:
                    return mmi;
            }
            return mmi;
        }

        private static int GetEdge(RECT rc)
        {
            int uEdge = -1;

            if (rc.top == rc.left && rc.bottom > rc.right)
            {
                uEdge = (int)ABEdge.ABE_LEFT;
            }
            else if (rc.top == rc.left && rc.bottom < rc.right)
            {
                uEdge = (int)ABEdge.ABE_TOP;
            }
            else if (rc.top > rc.left)
            {
                uEdge = (int)ABEdge.ABE_BOTTOM;
            }
            else
            {
                uEdge = (int)ABEdge.ABE_RIGHT;
            }

            return uEdge;
        }

        public static void WindowInitialized(Window window)
        {
            IntPtr handle = (new WindowInteropHelper(window)).Handle;
            HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(WindowProc));
        }

        private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case 0x0024:
                    WmGetMinMaxInfo(hwnd, lParam);
                    handled = true;
                    break;
            }

            return (IntPtr)0;
        }

        private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
        {
            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
            IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitorContainingApplication != System.IntPtr.Zero)
            {
                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitorContainingApplication, monitorInfo);
                RECT rcWorkArea = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
                mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;                                //maximum drag X size for the window
                mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;                                //maximum drag Y size for the window
                mmi.ptMinTrackSize.x = 200;                                            //minimum drag X size for the window
                mmi.ptMinTrackSize.y = 40;                                             //minimum drag Y size for the window
                mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi); //need to adjust sizing if taskbar is set to autohide
            }
            Marshal.StructureToPtr(mmi, lParam, true);
        }

        public enum ABEdge
        {
            ABE_LEFT = 0,
            ABE_TOP = 1,
            ABE_RIGHT = 2,
            ABE_BOTTOM = 3
        }

        public enum ABMsg
        {
            ABM_NEW = 0,
            ABM_REMOVE = 1,
            ABM_QUERYPOS = 2,
            ABM_SETPOS = 3,
            ABM_GETSTATE = 4,
            ABM_GETTASKBARPOS = 5,
            ABM_ACTIVATE = 6,
            ABM_GETAUTOHIDEBAR = 7,
            ABM_SETAUTOHIDEBAR = 8,
            ABM_WINDOWPOSCHANGED = 9,
            ABM_SETSTATE = 10
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct APPBARDATA
        {
            public int cbSize;
            public IntPtr hWnd;
            public int uCallbackMessage;
            public int uEdge;
            public RECT rc;
            public bool lParam;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
            public RECT rcMonitor = new RECT();
            public RECT rcWork = new RECT();
            public int dwFlags = 0;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;

            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

在这段代码中:

    private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
    {
        MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
        IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

        if (monitorContainingApplication != System.IntPtr.Zero)
        {
            MONITORINFO monitorInfo = new MONITORINFO();
            GetMonitorInfo(monitorContainingApplication, monitorInfo);
            RECT rcWorkArea = monitorInfo.rcWork;
            RECT rcMonitorArea = monitorInfo.rcMonitor;
            mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
            mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
            mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
            mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
            mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;                                //maximum drag X size for the window
            mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;                                //maximum drag Y size for the window
            mmi.ptMinTrackSize.x = 200;                                            //minimum drag X size for the window
            mmi.ptMinTrackSize.y = 40;                                             //minimum drag Y size for the window
            mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi); //need to adjust sizing if taskbar is set to autohide
        }
        Marshal.StructureToPtr(mmi, lParam, true);
    }

如果我改变:

        mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
        mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

收件人(用于测试目的):

        mmi.ptMaxSize.x = 1920 //Math.Abs(rcWorkArea.right - rcWorkArea.left);
        mmi.ptMaxSize.y = 1080 //Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

然后最大化的窗口离屏幕大约 8 个像素。但是,如果我将其更改为:

        mmi.ptMaxSize.x = 1919 //Math.Abs(rcWorkArea.right - rcWorkArea.left);
        mmi.ptMaxSize.y = 1080 //Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

那么最大化的窗口是完美的,只是宽度太小了一个像素。有谁知道为什么会这样?

我有一些新信息要添加。我的 wpf 窗口使用 WindowStyle = none,所以为了添加动画,我创建了一个 Window_Loaded 事件并重置窗口的样式,如下所示:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        hWnd = new WindowInteropHelper(this).Handle;
        //IntPtr myStyle = new IntPtr(WS.WS_CAPTION | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX | WS.WS_SYSMENU | WS.WS_SIZEBOX);
        IntPtr myStyle = new IntPtr(WS.WS_CAPTION | WS.WS_MINIMIZEBOX | WS.WS_SYSMENU | WS.WS_SIZEBOX);
        SetWindowLongPtr(new HandleRef(null, hWnd), GWL_STYLE, myStyle);
    }

似乎 WS_MAXIMIZE 样式中的某些内容覆盖了最大化大小,因为如果我按照上面的代码所示删除此样式,最大化的窗口就完美匹配了!但是,我也失去了 WS_MAXIMIZE 窗口样式提供的航空捕捉功能和双击标题以最大化。如果我能找到一种在不传递 WS_MAXIMIZE 样式的情况下添加回 aero snap 的方法,或者找到一种防止 WS_MAXIMIZE 样式覆盖我的 MINMAXINFO 的方法,我的问题就会得到解决。

我发现this post 提供了有关此问题的更多信息。看来我的猜想是对的。问题是如果 WM_GETMINMAXINFO 中提供的大小小于监视器的尺寸,窗口管理器只会重新计算最大化的窗口大小。所以尺寸是正确的,它们只是没有被使用,因为它们完全等于显示器的尺寸。相反,窗口管理器使用默认的最大化尺寸,它还在每边添加 4 个像素来说明边框(我什至没有)。

更新:我已经尝试了几乎所有我能想到的 WndProc 覆盖,我仍在寻找解决这个问题的方法,但我有一些新信息要添加,可能会有所帮助。

我已经使用 Spy++ 检查了 Twitch Launcher 的窗口,该窗口具有适当的最大化行为。我注意到它是一个中间 D3D 窗口,其中主应用程序窗口是禁用的父窗口的子窗口。

我对 windows api 很陌生,所以我有点不知所措,但我想知道子窗口是否设置为仅填充屏幕范围内的禁用父窗口的尺寸,以及无法看到的父窗口可能仍然在每一侧从屏幕延伸 8 个像素。我真的不知道如何在一个窗口中创建一个窗口,但我将尝试使用它,看看它是如何进行的。如果我让它工作,我会回复答案。如果您有任何可以帮助我的补充,请告诉我。

【问题讨论】:

  • 你有什么问题?
  • 这是很正常的,最大化的窗口在屏幕外扩展了几个像素。这就是窗口管理器“移除”窗口边框的方式。 "Why are the dimensions of a maximized window larger than the monitor?"
  • 您是否正在移除窗口镶边并呈现您自己的最小/最大/等按钮?
  • 我知道窗口延伸到屏幕外是正常的,但我正在尝试通过自己处理 MINMAXINFO 来解决这个问题。您可以在视频中看到,如果启用了自动隐藏任务栏,它可以正常工作,但在关闭自动隐藏时它不能正常工作。我的问题是为什么从最大尺寸的 x 坐标中减去 1 会使最大窗口完美契合(但当然它偏离了 1 个像素)但不理会它或减去 0 会导致窗口离开屏幕 8 个像素(因为它通常是)。而且它的窗口样式没有我自己的最小/最大按钮。
  • 试试这个 api -- SetWindowPlacement。以及msdn 上的相关问题

标签: c# wpf winapi


【解决方案1】:

如果您将窗口镶边配置为隐藏操作系统提供的窗口控件以支持创建您自己的窗口控件,那么您需要在窗口内容模板最大化状态下调整边框大小,以使窗口正确显示在屏幕边界内.我在我的 Window 内容模板中使用以下边框样式

<ControlTemplate TargetType="{x:Type Window}">
   <Border Background="{TemplateBinding Background}">
      <Border.Style>
         <!-- This style solves the over scanning like problem that happens when you 
              maximize a window that has had its OS provided window decorations removed.  
              This essentially makes the border of the window thicker to make up for the
              over scan when in the maximized state.  When removed from that state, the 
              border goes back to 0.-->
              <Style TargetType="{x:Type Border}">
                 <Setter Property="BorderThickness" Value="0.8"/>
                 <Setter Property="BorderBrush" Value="{StaticResource SecondaryColorBrush}"/>
                 <Setter Property="CornerRadius" Value="1"/>
                 <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
                       <Setter Property="BorderThickness" Value="7"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=IsActive}" Value="True">
                       <Setter Property="BorderBrush" Value="{StaticResource AccentColorBrush}"/>
                    </DataTrigger>
                 </Style.Triggers>
              </Style>
           </Border.Style>
           <Grid>
           ...

希望这能解决您遇到的问题...

【讨论】:

  • "如果您正在配置窗口镶边"。问题没有说明这一点。这似乎是推测性的。
  • @DavidHeffernan 引用的视频显示了带有非标准窗口控件的窗口。这是我为解决他所描述的问题所做的事情。我在 cmets 中问了这个问题,但没有收到回复。没想到把一些对我有帮助的信息放在一个非常相似(如果不一样)的问题上会是一件坏事。
  • 我正在使用 WindowChrome 类,但是在 XAML 中这样做是行不通的,因为我必须处理 MINMAXINFO 以获得与自动隐藏任务栏的兼容性,因为出于某种原因,使用 WindowChrome 会导致最大化窗口不使用自动隐藏任务栏。它也不能通过 aero peek 修复故障,而处理 MINMAXINFO 可以。您的建议实际上是一个很好的建议,这是我过去解决此问题的方式,但不幸的是不适用于自动隐藏任务栏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-24
  • 1970-01-01
  • 1970-01-01
  • 2012-02-29
  • 1970-01-01
相关资源
最近更新 更多