Максимизированное окно WPF примерно на 8 пикселей больше с каждой стороны - PullRequest
0 голосов
/ 10 июня 2019

Я пытаюсь исправить следующие проблемы с максимизацией моего окна WindowChrome:

  1. Развернутое окно выходит за пределы экрана примерно на 8 пикселей с каждой стороны.
  2. Aero Peek показывает прозрачную область сверху и слева около 8 пикселей.
  3. Панель задач автоматического скрытия не работает с развернутым окном.

Я нашел отличную статью , в которой объясняется, как решить проблему с панелью задач. Кстати, при использовании этого решения Aero Peek, панель автоматического скрытия задач и максимизация окна работают, но только когда панель задач настроена на автоматическое скрытие. Если не задано автоматическое скрытие, код просто возвращает стандарт MINMAXINFO.

Странно то, что стандартный MINMAXINFO, похоже, должен обеспечить правильное размещение и размер окна при максимизации. Я знаю это, потому что если я просто вычту 1 пиксель из значения x положения MINMAXINFO или максимального размера, развернутое окно будет в правильном месте, но его ширина будет ровно на 1 пиксель слишком маленькой.

Однако, если я оставлю стандартный MINMAXINFO один, окно будет слишком большим, намного больше, чем один пиксель. Это похоже на то, как будто добавление или вычитание чего-либо из ptMaxPosition или ptMaxSize.x заставляет окно использовать пользовательский MINMAXINFO. Я попытался сложить и вычесть ноль, но, к сожалению, это не сработало.

Вот видео , которое показывает проблему и, мы надеемся, поможет прояснить проблему.

Вот код, который я использую:

    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 или способ предотвратить переопределение стиля WS_MAXIMIZE моего MINMAXINFO, мои проблемы были бы решены.

Я нашел это сообщение с дополнительной информацией по этой проблеме. Кажется, мои подозрения были верны. Проблема заключается в том, что оконный менеджер будет пересчитывать максимальный размер окна только в том случае, если размер, указанный в WM_GETMINMAXINFO, меньше размеров монитора. Таким образом, размеры правильные, они просто не используются, потому что они точно равны размерам монитора. Вместо этого оконный менеджер использует любые размеры максимизации по умолчанию, которые также добавляют 4 пикселя с каждой стороны для учета границ (которых у меня даже нет).

Обновление: я перепробовал почти все, что мог придумать с переопределениями WndProc, и я все еще ищу решение этой проблемы, но у меня есть некоторая новая информация, которую можно добавить, которая могла бы помочь.

Я просмотрел окно Twitch Launcher, которое имеет правильное поведение максимизации, со Spy ++. Я заметил, что это промежуточное окно D3D, где главное окно приложения является дочерним окном отключенного родительского окна.

Я очень новичок в API Windows, так что я в порядке над головой, но мне интересно, настроено ли дочернее окно только для заполнения размеров отключенного родительского окна, которые находятся в пределах экрана, и родительское окно, которое не видно, вероятно, все еще выходит за пределы экрана на 8 пикселей с каждой стороны. Я действительно не знаю, как создать окно в окне, но я собираюсь поэкспериментировать с ним и посмотреть, как оно работает. Если у меня получится, я отправлю ответ. Если у вас есть что добавить, что может помочь мне, пожалуйста, дайте мне знать.

1 Ответ

0 голосов
/ 10 июня 2019

Если вы конфигурируете оконный хром, чтобы скрыть предоставляемые ОС элементы управления окнами в пользу создания своих собственных, вам нужно будет отрегулировать размер границы в максимизированном состоянии шаблона содержимого Windows, чтобы окно правильно отображалось в границах экрана. , Я использую следующий стиль рамки в моем шаблоне содержимого 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>
           ...

Надеюсь, это решит проблему, с которой вы столкнулись ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...