Есть ли способ изменить цвет NonClientFrameEdges или сделать их невидимыми (особенно с WPF /.NET)? - PullRequest
0 голосов
/ 06 августа 2020

Сводка цели

Я пытаюсь создать окно WPF с настраиваемым заголовком окна (чтобы я мог нарисовать строку заголовка и добавить туда элементы управления). Мне удалось это сделать с классом WPF Window Chrome, но он содержит много ошибок. Мне удалось обойти большинство из них, установив для свойства Window Chrome NoneClientFrameEdges любое значение, кроме None, но это привело к появлению новой ошибки в процессе. Там есть неприглядная граница толщиной ~ 1 пиксель, где установлено свойство NonClientFrameEdge. Вы можете увидеть это на видео, но очень тускло. Я хочу либо установить прозрачный цвет, либо найти способ полностью отключить его отрисовку. Проблема в том, что на самом деле я не могу удалить NonClientFrameEdge, потому что это требуется для исправления ошибок, о которых я упоминал ранее (подробнее ниже).

Подробное объяснение проблемы (как лучше всего, насколько я понимаю)

Насколько я понимаю, стандартное окно разделено как минимум на две части: клиентскую область и неклиентскую область. Клиентская область - это часть окна, которую вы можете легко настроить, куда вы добавляете элементы управления и другое содержимое окна. Неклиентская область - это часть окна, которая включает в себя рамку, состоящую из границ изменения размера и заголовка. В нормальных условиях вы не можете изменять неклиентскую область.

Класс Window Chrome в WPF обеспечивает простой способ настройки неклиентской область окна WPF. Это позволяет вам создавать собственные заголовки и размещать там элементы управления, как в современных корпоративных приложениях, таких как Visual Studio и Microsoft Office. Проблема в том, что использование Window Chrome вызывает множество ошибок. Вот список тех, которые я нашел до сих пор:

  1. Когда вы сворачиваете окно из развернутого состояния и наводите на него курсор на панели задач, в итоговом окне предварительного просмотра появляется ~ 8 пикселей. пустого места сверху и слева вверху. Если вы щелкнете по нему, чтобы переместить его на передний план, он вернется в нужное место. Даже в Visual Studio и Microsoft Office есть эта ошибка. (вызвано тем, что значение GlassFrameThickness равно 0 в классе Window Chrome)

  2. Эффект прозрачного стеклянного прямоугольника, который вы получаете при наведении курсора на кнопку аэродинамического обзора для «взгляда на рабочий стол», больше не работает. Вы просто видите пустое пространство, никаких контуров приложения. (вызвано тем, что для класса Window Chrome установлено значение 0 GlassFrameThickness)

  3. Окно дрожит при изменении размера из любого угла, кроме правого нижнего. Я читал это потому, что изменение размера из этих углов приводит к изменению положения окна. (вызвано тем, что значение NonClientFrameEdges равно 0, чтобы избежать этого, необходимо установить по крайней мере 1 край)

  4. Окно искажается и размывается во время эффекта масштабирования фокуса при выборе свернутого окна просмотр задач. Даже у многих корпоративных приложений есть эта проблема. (Adobe Photoshop, Visual Studio и несколько программ запуска игр). (вызвано значением GlassFrameThickness, равным 0, значением NonClientFrameEdges, равным None, либо их комбинацией)

Хакерское решение и видео до и после

Я заметил, что проблемы 1 и 2 можно исправить, установив для GlassFrameThickness ненулевое значение. Проблемы 3 и 4 можно устранить, задав для свойства NonClientFrameEdges класса Window Chrome любое значение, кроме None. В этом видео показаны все проблемы, перечисленные выше, а также их внешний вид до и после изменения этого свойства: https://www.youtube.com/watch?v=z7O28aEPygg

Примечание. Видео обрывается внезапно из-за моих плохих навыков редактирования, но он должен показать все необходимые вопросы. Вам нужно будет очень внимательно посмотреть на нижнюю часть приложения во второй половине видео, чтобы увидеть серый / белый NonClientFrameEdge размером 1 пиксель, но это заметно. Это то, что я пытаюсь исправить.

Проблема в том, что установка NonClientFrameEdges на любое значение, отличное от None, буквально добавляет к вашему окну край на 2-3 пикселя, и это визуально очевидно. Установка GlassFrameThickness на 1 на том же крае, что и NonClientFrameEdge, значительно снижает видимость, но это все еще заметно на 1 пиксель или около того. Например, если для NonClientFrameEdge задано значение Bottom, GlassFrameThickness следует установить в «0, 0, 0, 1».

Минимальный пример Вы можете воспроизвести проблему, создав файл. NET или. NET Core WPF project и добавление следующего кода в файл представления XAML окна:

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0 0 0 1" CornerRadius="0" CaptionHeight="38" UseAeroCaptionButtons="False" ResizeBorderThickness="5" NonClientFrameEdges="Bottom" />
</WindowChrome.WindowChrome>

Вот репозиторий Git с минимальным проектом для удобства, если вы хотите его протестировать, но не Не хочется печатать весь код. Он включает в себя некоторые дополнительные вещи, такие как шаблон для ViewModel, запуск команд, кнопки для min / max / close и кнопку для добавления границы для тестирования (делает такие вещи, как дрожание изменения размера более очевидным). Он также включает перехватчик в WndPro c с Pinvoke на тот случай, если вы захотите поэкспериментировать с WindowsAPI: https://github.com/cjfcode/WindowProject

1 Ответ

1 голос
/ 06 августа 2020

Ключ к решению этого - связать оконный хук с обработчиком для сообщений WM_NCCALCSIZE (0x83) и WM_NCPAINT (0x85).

WM_NCPAINT позволит вам удалить нижнюю границу в один пиксель, вызвав DwmExtendFrameIntoClientArea. В приведенном ниже коде я заключил этот вызов в метод под названием RemoveFrame.

WM_NCCALCSIZE позволит вам изменить размер клиентской области окна, восстанавливая дополнительное пространство, которое WindowChrome установил используя GlassFrameThickness="0,0,0,1" и NonClientFrameEdges="Bottom".

Я объединил эту функциональность в поведение XAML.

Вот последний код, который решит вашу проблему:

WindowChromeLoadedBehavior

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Shell;
using Microsoft.Xaml.Behaviors;

namespace WpfApp1
{
    public class WindowChromeLoadedBehavior : Behavior<FrameworkElement>
    {
        private Window window;

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += OnLoaded;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            window = Window.GetWindow(AssociatedObject);

            if (window == null) return;

            Task.Delay(5).ContinueWith(_ =>
            {
                Dispatcher.Invoke(() =>
                {
                    var oldWindowChrome = WindowChrome.GetWindowChrome(window);

                    if (oldWindowChrome == null) return;

                    var newWindowChrome = new WindowChrome
                    {
                        CaptionHeight = oldWindowChrome.CaptionHeight,
                        CornerRadius = oldWindowChrome.CornerRadius,
                        GlassFrameThickness = new Thickness(0, 0, 0, 1),
                        NonClientFrameEdges = NonClientFrameEdges.Bottom,
                        ResizeBorderThickness = oldWindowChrome.ResizeBorderThickness,
                        UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
                    };

                    WindowChrome.SetWindowChrome(window, newWindowChrome);
                });
            });

            var hWnd = new WindowInteropHelper(window).Handle;
            HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case NativeMethods.WM_NCPAINT:
                    RemoveFrame();
                    handled = false;
                    break;

                case NativeMethods.WM_NCCALCSIZE:

                    handled = false;

                    var rcClientArea = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
                    rcClientArea.Bottom += (int)(WindowChromeHelper.WindowResizeBorderThickness.Bottom / 2);
                    Marshal.StructureToPtr(rcClientArea, lParam, false);

                    var retVal = IntPtr.Zero;
                    if (wParam == new IntPtr(1))
                    {
                        retVal = new IntPtr((int)NativeMethods.WVR.REDRAW);
                    }
                    return retVal;
            }

            return IntPtr.Zero;
        }

        private void RemoveFrame()
        {
            if (Environment.OSVersion.Version.Major >= 6 && NativeMethods.IsDwmAvailable())
            {
                if (NativeMethods.DwmIsCompositionEnabled() && SystemParameters.DropShadow)
                {
                    NativeMethods.MARGINS margins;

                    margins.bottomHeight = -1;
                    margins.leftWidth = 0;
                    margins.rightWidth = 0;
                    margins.topHeight = 0;

                    var helper = new WindowInteropHelper(window);

                    NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
                }
            }
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
            public static RECT Empty;

            public int Width => Math.Abs(Right - Left);

            public int Height => (Bottom - Top);

            public RECT(int left, int top, int right, int bottom)
            {
                Left = left;
                Top = top;
                Right = right;
                Bottom = bottom;
            }

            public RECT(RECT rcSrc)
            {
                Left = rcSrc.Left;
                Top = rcSrc.Top;
                Right = rcSrc.Right;
                Bottom = rcSrc.Bottom;
            }

            public RECT(Rectangle rectangle) : this(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom)
            {
            }

            public bool IsEmpty
            {
                get
                {
                    if (Left < Right)
                    {
                        return (Top >= Bottom);
                    }
                    return true;
                }
            }

            public override string ToString()
            {
                if (this == Empty)
                {
                    return "RECT {Empty}";
                }
                return string.Concat("RECT { left : ", Left, " / top : ", Top, " / right : ", Right, " / bottom : ", Bottom, " }");
            }

            public override bool Equals(object obj)
            {
                return ((obj is Rect) && (this == ((RECT)obj)));
            }

            public override int GetHashCode()
            {
                return ((Left.GetHashCode() + Top.GetHashCode()) + Right.GetHashCode()) + Bottom.GetHashCode();
            }

            public static bool operator ==(RECT rect1, RECT rect2)
            {
                return ((((rect1.Left == rect2.Left) && (rect1.Top == rect2.Top)) && (rect1.Right == rect2.Right)) && (rect1.Bottom == rect2.Bottom));
            }

            public static bool operator !=(RECT rect1, RECT rect2)
            {
                return !(rect1 == rect2);
            }

            static RECT()
            {
                Empty = new RECT();
            }
        }

    }
}

WindowChromeHelper

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

namespace WpfApp1
{
    public static class WindowChromeHelper
    {
        public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);

        /// <summary>
        /// Gets the properly adjusted window resize border thickness from system parameters.
        /// </summary>
        public static Thickness WindowResizeBorderThickness
        {
            get
            {
                var dpix = GetDpi(GetDeviceCapsIndex.LOGPIXELSX);
                var dpiy = GetDpi(GetDeviceCapsIndex.LOGPIXELSY);

                var dx = GetSystemMetrics(GetSystemMetricsIndex.CXFRAME);
                var dy = GetSystemMetrics(GetSystemMetricsIndex.CYFRAME);

                // This adjustment is needed since .NET 4.5 
                var d = GetSystemMetrics(GetSystemMetricsIndex.SM_CXPADDEDBORDER);
                dx += d;
                dy += d;

                var leftBorder = dx / dpix;
                var topBorder = dy / dpiy;

                return new Thickness(leftBorder, topBorder, leftBorder, topBorder);
            }
        }

        [DllImport("user32.dll")]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        [DllImport("user32.dll")]
        private static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("gdi32.dll")]
        private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

        private static float GetDpi(GetDeviceCapsIndex index)
        {
            var desktopWnd = IntPtr.Zero;
            var dc = GetDC(desktopWnd);
            float dpi;
            try
            {
                dpi = GetDeviceCaps(dc, (int)index);
            }
            finally
            {
                ReleaseDC(desktopWnd, dc);
            }
            return dpi / 96f;
        }

        private enum GetDeviceCapsIndex
        {
            LOGPIXELSX = 88,
            LOGPIXELSY = 90
        }

        [DllImport("user32.dll")]
        private static extern int GetSystemMetrics(GetSystemMetricsIndex nIndex);

        private enum GetSystemMetricsIndex
        {
            CXFRAME = 32,
            CYFRAME = 33,
            SM_CXPADDEDBORDER = 92
        }

    }
}

NativeMethods

using System;
using System.Runtime.InteropServices;

namespace WpfApp1
{
    public static class NativeMethods
    {
        public const int WM_NCCALCSIZE = 0x83;
        public const int WM_NCPAINT = 0x85;

        [DllImport("kernel32", SetLastError = true)]
        private static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("dwmapi.dll", PreserveSig = false)]
        public static extern bool DwmIsCompositionEnabled();

        [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [StructLayout(LayoutKind.Sequential)]
        public struct MARGINS
        {
            public int leftWidth;
            public int rightWidth;
            public int topHeight;
            public int bottomHeight;
        }

        private delegate int DwmExtendFrameIntoClientAreaDelegate(IntPtr hwnd, ref MARGINS margins);

        public static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins)
        {
            var hModule = LoadLibrary("dwmapi");

            if (hModule == IntPtr.Zero)
            {
                return 0;
            }

            var procAddress = GetProcAddress(hModule, "DwmExtendFrameIntoClientArea");

            if (procAddress == IntPtr.Zero)
            {
                return 0;
            }

            var delegateForFunctionPointer = (DwmExtendFrameIntoClientAreaDelegate)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(DwmExtendFrameIntoClientAreaDelegate));

            return delegateForFunctionPointer(hwnd, ref margins);
        }

        public static bool IsDwmAvailable()
        {
            if (LoadLibrary("dwmapi") == IntPtr.Zero)
            {
                return false;
            }
            return true;
        }

        internal enum WVR
        {
            ALIGNTOP = 0x0010,
            ALIGNLEFT = 0x0020,
            ALIGNBOTTOM = 0x0040,
            ALIGNRIGHT = 0x0080,
            HREDRAW = 0x0100,
            VREDRAW = 0x0200,
            VALIDRECTS = 0x0400,
            REDRAW = HREDRAW | VREDRAW
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...