大数跨境
0
0

WIN11 Snap 是什么?自定义 WINDOW 如何使用 Snap 功能?

WIN11 Snap 是什么?自定义 WINDOW 如何使用 Snap 功能? Win开发者
2025-12-03
6
导读:WIN11 Snap 是什么?自定义 WINDOW 如何使用 Snap 功能?控件名:Snap作 者:W

WIN11 Snap 是什么?自定义 WINDOW 如何使用 Snap 功能?

控件名:Snap

作   者:WPFDevelopersOrg - 驚鏵

原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers

码云链接[2]:https://gitee.com/WPFDevelopersOrg/WPFDevelopers

  • 框架支持.NET4 至 .NET8
  • Visual Studio 2022;

Issue:Window 控件在 Win11 下不支持 Snap 功能[3]

Snap 是什么?
  • Snap Layouts 是 Windows 11 的一项新功能,但是系统版本号必须 ≥ 21H2 。

  • Snap Layouts

    • Snap Layouts 提供 6 种不同的布局;
    • 窗口排列 2 种
    • 窗口排列 3 种
    • 窗口排列 1 种
    • 提供了多种预设的窗口布局,可以将窗口快速调整到屏幕的左侧、右侧、上方、下方,甚至是四分之一屏幕区域;
    • 可以通过将窗口拖动到屏幕边缘来启动 Snap,或使用快捷键(Win + 左/右箭头、Win + 上/下箭头)来实现;
    • 在应用程序或窗口中按下「Win + Z」快捷键,或是将鼠标光标悬停在窗口最大化按钮上以显示 Snap Layout 菜单。
    • 分屏允许通过将屏幕的一半或四分之一,让多任务处理变得更加流畅,对于大屏幕或多显示器的用户尤其有用;
如何在 WPF 中自定义 Window 支持 SnapLayout 功能
1. 修改 Window.cs
  • 重写 OnSourceInitialized,实现 Win32 消息钩子 HwndSourceHook;
  • WM_NCHITTEST:用于检测鼠标在 Window 的哪个区域;
  • WM_NCLBUTTONDOWN:鼠标左键按下,是否在标题栏按钮;
  • 处理 Snap Layout 消息 HandleSnapLayoutMessage;
    • 获取当前最大化还是还原按钮;
    • 通过 lParam 获取鼠标屏幕坐标,是否在最大化或者还原按钮上;
    • 然后返回 HTMAXBUTTON 通知系统鼠标现在在最大化按钮上;
using Microsoft.Windows.Shell;
using System;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using WPFDevelopers.Controls;
using WPFDevelopers.Core.Helpers;
using WPFDevelopers.Helpers;

namespaceWPFDevelopers.Net40
{
    [TemplatePart(Name = TitleBarIcon, Type = typeof(Button))]
    [TemplatePart(Name = HighTitleMaximizeButton, Type = typeof(Button))]
    [TemplatePart(Name = HighTitleRestoreButton, Type = typeof(Button))]
    [TemplatePart(Name = TitleBarMaximizeButton, Type = typeof(Button))]
    [TemplatePart(Name = TitleBarRestoreButton, Type = typeof(Button))]
    publicclassWindow : System.Windows.Window
    {
        privateconststring TitleBarIcon = "PART_TitleBarIcon";
        privateconststring HighTitleMaximizeButton = "PART_MaximizeButton";
        privateconststring HighTitleRestoreButton = "PART_RestoreButton";
        privateconststring TitleBarMaximizeButton = "PART_TitleBarMaximizeButton";
        privateconststring TitleBarRestoreButton = "PART_TitleBarRestoreButton";
        private WindowStyle _windowStyle;
        private Button _titleBarIcon;
        private Button _highTitleMaximizeButton;
        private Button _highTitleRestoreButton;
        private Button _titleBarMaximizeButton;
        private Button _titleBarRestoreButton;
        private IntPtr hWnd;

        publicstaticreadonly DependencyProperty TitleHeightProperty =
            DependencyProperty.Register("TitleHeight"typeof(double), typeof(Window), new PropertyMetadata(50d));

        publicstaticreadonly DependencyProperty NoChromeProperty =
            DependencyProperty.Register("NoChrome"typeof(bool), typeof(Window), new PropertyMetadata(false));

        publicstaticreadonly DependencyProperty TitleBarProperty =
            DependencyProperty.Register("TitleBar"typeof(object), typeof(Window), new PropertyMetadata(null));

        publicstaticreadonly DependencyProperty TitleBackgroundProperty =
           DependencyProperty.Register("TitleBackground"typeof(Brush), typeof(Window), new PropertyMetadata(null));

        publicstaticreadonly DependencyProperty TitleBarModeProperty =
            DependencyProperty.Register("TitleBarMode"typeof(TitleBarMode), typeof(Window), new PropertyMetadata(TitleBarMode.Normal));

        static Window()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Window), new FrameworkPropertyMetadata(typeof(Window)));
        }

        public Window()
        {
            WPFDevelopers.Resources.ThemeChanged += Resources_ThemeChanged;
            CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindow));
            CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindow,
                CanResizeWindow));
            CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindow,
                CanMinimizeWindow));
            CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, RestoreWindow,
                CanResizeWindow));
        }

        private void Resources_ThemeChanged(ThemeType currentTheme)
        {
            var isDark = currentTheme == ThemeType.Dark ? true : false;
            var source = (HwndSource)PresentationSource.FromVisual(this);
            Win32.EnableDarkModeForWindow(source, isDark);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _windowStyle = WindowStyle;
            _titleBarIcon = GetTemplateChild(TitleBarIcon) as Button;
            if (_titleBarIcon != null)
            {
                _titleBarIcon.MouseDoubleClick -= Icon_MouseDoubleClick;
                _titleBarIcon.MouseDoubleClick += Icon_MouseDoubleClick;
            }
            _highTitleMaximizeButton = GetTemplateChild(HighTitleMaximizeButton) as Button;
            _highTitleRestoreButton = GetTemplateChild(HighTitleRestoreButton) as Button;
            _titleBarMaximizeButton = GetTemplateChild(TitleBarMaximizeButton) as Button;
            _titleBarRestoreButton = GetTemplateChild(TitleBarRestoreButton) as Button;
        }

        private void Icon_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
                Close();
        }

        publicdouble TitleHeight
        {
            get => (double)GetValue(TitleHeightProperty);
            set => SetValue(TitleHeightProperty, value);
        }

        publicbool NoChrome
        {
            get => (bool)GetValue(NoChromeProperty);
            set => SetValue(NoChromeProperty, value);
        }

        publicobject TitleBar
        {
            get => (object)GetValue(TitleBarProperty);
            set => SetValue(TitleBarProperty, value);
        }

        public Brush TitleBackground
        {
            get => (Brush)GetValue(TitleBackgroundProperty);
            set => SetValue(TitleBackgroundProperty, value);
        }

        public TitleBarMode TitleBarMode
        {
            get => (TitleBarMode)GetValue(TitleBarModeProperty);
            set => SetValue(TitleBarModeProperty, value);
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            hWnd = new WindowInteropHelper(this).Handle;
            HwndSource.FromHwnd(hWnd).AddHook(WindowProc);
            if (TitleBarMode == TitleBarMode.Normal)
                TitleHeight = SystemParameters2.Current.WindowNonClientFrameThickness.Top;
        }

        protected override void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);
            if (SizeToContent == SizeToContent.WidthAndHeight)
                InvalidateMeasure();
        }

        #region Window Commands

        private void CanResizeWindow(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = ResizeMode == ResizeMode.CanResize || ResizeMode == ResizeMode.CanResizeWithGrip;
        }

        private void CanMinimizeWindow(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = ResizeMode != ResizeMode.NoResize;
        }

        private void CloseWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.CloseWindow(this);
        }

        private void MaximizeWindow(object sender, ExecutedRoutedEventArgs e)
        {
            if (WindowState == WindowState.Normal)
            {
                WindowStyle = WindowStyle.SingleBorderWindow;
                WindowState = WindowState.Maximized;
                WindowStyle = WindowStyle.None;
            }
        }

        private void MinimizeWindow(object sender, ExecutedRoutedEventArgs e)
        {
            Win32.SendMessage(hWnd, WindowsMessageCodes.WM_SYSCOMMAND, new IntPtr(WindowsMessageCodes.SC_MINIMIZE), IntPtr.Zero);
        }

        private void RestoreWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.RestoreWindow(this);
        }


        private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case WindowsMessageCodes.WM_SYSCOMMAND:
                    if (wParam.ToInt32() == WindowsMessageCodes.SC_MINIMIZE)
                    {
                        _windowStyle = WindowStyle;
                        if (WindowStyle != WindowStyle.SingleBorderWindow)
                            WindowStyle = WindowStyle.SingleBorderWindow;
                        WindowState = WindowState.Minimized;
                        handled = true;
                    }
                    elseif (wParam.ToInt32() == WindowsMessageCodes.SC_RESTORE)
                    {
                        WindowState = WindowState.Normal;
                        WindowStyle = WindowStyle.None;
                        if (WindowStyle.None != _windowStyle)
                            WindowStyle = _windowStyle;
                        handled = true;
                    }
                    break;
                case WindowsMessageCodes.WM_NCHITTEST:
                case WindowsMessageCodes.WM_NCLBUTTONDOWN:
                    try
                    {
                        if (!OSVersionHelper.IsSnapLayoutSupported()
                            ||
                            ResizeMode == ResizeMode.NoResize 
                            || 
                            ResizeMode == ResizeMode.CanMinimize)
                            break;
                        else
                        {
                            IntPtr result = IntPtr.Zero;
                            if (HandleSnapLayoutMessage(msg, lParam, ref result))
                            {
                                handled = true;
                                return result;
                            }
                        }
                    }
                    catch (OverflowException)
                    {
                        handled = true;
                    }
                    break;
            }
            return IntPtr.Zero;
        }

        private bool HandleSnapLayoutMessage(int msg, IntPtr lParam, ref IntPtr result)
        {
            Button button = TitleBarMode == TitleBarMode.Normal
                ? (WindowState != WindowState.Maximized ? _titleBarMaximizeButton : _titleBarRestoreButton)
                : (WindowState != WindowState.Maximized ? _highTitleMaximizeButton : _highTitleRestoreButton);

            if (button == null || button.ActualWidth <= 0 || button.ActualHeight <= 0)
                returnfalse;
            var contentPresenter = button.Template.FindName("PART_ContentPresenter", button) as ContentPresenter;
            var x = lParam.ToInt32() & 0xffff;
            var y = lParam.ToInt32() >> 16;
            var dpiX = OSVersionHelper.DeviceUnitsScalingFactorX;
            var rect = new Rect(button.PointToScreen(new Point()), new Size(button.ActualWidth * dpiX, button.ActualHeight * dpiX));
            var point = new Point(x, y);

            if (msg == WindowsMessageCodes.WM_NCHITTEST && contentPresenter != null)
            {
                if (!rect.Contains(point))
                {
                    if(contentPresenter.Opacity != 0.7)
                        contentPresenter.Opacity = 0.7;
                    returnfalse;
                }
                contentPresenter.Opacity = 1;
                result = new IntPtr(OSVersionHelper.HTMAXBUTTON);
            }
            elseif (msg == WindowsMessageCodes.WM_NCLBUTTONDOWN)
            {
                IInvokeProvider invokeProv = new ButtonAutomationPeer(button).GetPattern(PatternInterface.Invoke) as IInvokeProvider;
                invokeProv?.Invoke();
            }

            returntrue;
        }


        private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e)
        {
            var element = e.OriginalSource as FrameworkElement;
            if (element == null)
                return;

            var point = WindowState == WindowState.Maximized
                ? new Point(0, element.ActualHeight)
                : new Point(Left + BorderThickness.Left, element.ActualHeight + Top + BorderThickness.Top);
            point = element.TransformToAncestor(this).Transform(point);
            SystemCommands.ShowSystemMenu(this, point);
        }

        #endregion
    }
}
2. 修改 Window.xaml
<Style BasedOn="{x:Null}" TargetType="{x:Type wd:Window}">
    <Setter Property="Foreground" Value="{DynamicResource WD.RegularTextBrush}" />
    <Setter Property="Background" Value="{DynamicResource WD.BackgroundBrush}" />
    <Setter Property="BorderBrush" Value="{DynamicResource WD.WindowBorderBrush}" />
    <Setter Property="TitleBackground" Value="{DynamicResource WD.WindowBorderBrush}" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="UseLayoutRounding" Value="True" />
    <Setter Property="TextOptions.TextFormattingMode" Value="Ideal" />
    <Setter Property="WindowStyle" Value="None" />
    <Setter Property="FontFamily" Value="{DynamicResource WD.FontFamily}" />
    <Setter Property="shell:WindowChrome.WindowChrome">
        <Setter.Value>
            <shell:WindowChrome CaptionHeight="{Binding TitleHeight, RelativeSource={RelativeSource AncestorType=wd:Window}}" GlassFrameThickness="0,0,0,.1" />
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type wd:Window}">
                <Border
                    Name="PART_Border"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    SnapsToDevicePixels="True">

                    <Grid x:Name="LayoutRoot">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <control:SmallPanel
                            x:Name="PART_Normal"
                            Grid.Row="0"
                            Background="{TemplateBinding TitleBackground}">

                            <Grid Height="{TemplateBinding TitleHeight}">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <StackPanel Orientation="Horizontal">
                                    <Button
                                        x:Name="PART_TitleBarIcon"
                                        Margin="5,0,0,0"
                                        VerticalAlignment="Center"
                                        shell:WindowChrome.IsHitTestVisibleInChrome="True"
                                        Background="Transparent"
                                        BorderThickness="0"
                                        Visibility="{Binding Icon, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource ObjectNullToVisibilityConverter}}">

                                        <Image
                                            Width="{x:Static SystemParameters.SmallIconWidth}"
                                            Height="{x:Static SystemParameters.SmallIconHeight}"
                                            IsHitTestVisible="False"
                                            RenderOptions.BitmapScalingMode="HighQuality"
                                            Source="{TemplateBinding Icon}" />

                                    </Button>

                                    <ContentControl
                                        Margin="5,0,0,0"
                                        VerticalAlignment="Center"
                                        Content="{TemplateBinding Title}"
                                        FontSize="{DynamicResource {x:Static SystemFonts.CaptionFontSizeKey}}"
                                        Foreground="{DynamicResource WD.WindowTextBrush}"
                                        IsTabStop="False" />

                                </StackPanel>
                                <StackPanel
                                    Grid.Column="1"
                                    HorizontalAlignment="Right"
                                    VerticalAlignment="Top"
                                    shell:WindowChrome.IsHitTestVisibleInChrome="True"
                                    Orientation="Horizontal">

                                    <StackPanel x:Name="PART_TitleBarMinAndMax" Orientation="Horizontal">
                                        <Button
                                            x:Name="PART_TitleBarMinimizeButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.MinimizeWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Minimize], Source={x:Static resx:LanguageManager.Instance}}">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Rectangle
                                                    Width="10"
                                                    Height="1"
                                                    Margin="0,7,0,0"
                                                    VerticalAlignment="Bottom"
                                                    Fill="{DynamicResource WD.WindowTextBrush}" />

                                            </Grid>
                                        </Button>
                                        <Button
                                            x:Name="PART_TitleBarMaximizeButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.MaximizeWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Maximize], Source={x:Static resx:LanguageManager.Instance}}">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Path
                                                    Width="10"
                                                    Height="10"
                                                    HorizontalAlignment="Center"
                                                    VerticalAlignment="Center"
                                                    Data="{DynamicResource WD.WindowMaximizeGeometry}"
                                                    Fill="{DynamicResource WD.WindowTextBrush}"
                                                    Stretch="Uniform"
                                                    UseLayoutRounding="False" />

                                            </Grid>
                                        </Button>
                                        <Button
                                            x:Name="PART_TitleBarRestoreButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.RestoreWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Restore], Source={x:Static resx:LanguageManager.Instance}}"
                                            Visibility="Collapsed">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Path
                                                    Width="10"
                                                    Height="10"
                                                    HorizontalAlignment="Center"
                                                    VerticalAlignment="Center"
                                                    Data="{DynamicResource WD.WindowRestoreGeometry}"
                                                    Fill="{DynamicResource WD.WindowTextBrush}"
                                                    Stretch="Uniform"
                                                    UseLayoutRounding="False" />

                                            </Grid>
                                        </Button>
                                    </StackPanel>

                                    <Button
                                        Name="PART_TitleBarCloseButton"
                                        Command="{Binding Source={x:Static shell:SystemCommands.CloseWindowCommand}}"
                                        IsTabStop="False"
                                        Style="{DynamicResource WD.WindowButtonStyle}"
                                        ToolTip="{Binding [Close], Source={x:Static resx:LanguageManager.Instance}}">

                                        <Path
                                            Width="10"
                                            Height="10"
                                            HorizontalAlignment="Center"
                                            VerticalAlignment="Center"
                                            Data="{DynamicResource WD.WindowCloseGeometry}"
                                            Fill="{DynamicResource WD.WindowTextBrush}"
                                            Stretch="Uniform" />

                                    </Button>
                                </StackPanel>
                            </Grid>
                        </control:SmallPanel>
                        <control:SmallPanel
                            x:Name="PART_HighTitleBar"
                            Grid.Row="0"
                            Background="{TemplateBinding TitleBackground}"
                            Visibility="Collapsed">

                            <Grid
                                x:Name="PART_GridChrome"
                                Height="{TemplateBinding TitleHeight}"
                                Margin="10,0,0,0">

                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" MinWidth="30" />
                                </Grid.ColumnDefinitions>
                                <Image
                                    Width="23"
                                    Height="23"
                                    VerticalAlignment="Center"
                                    RenderOptions.BitmapScalingMode="HighQuality"
                                    Source="{TemplateBinding Icon}"
                                    Visibility="{TemplateBinding Icon,
                                                                 Converter={StaticResource ObjectNullToVisibilityConverter}}"
 />

                                <TextBlock
                                    x:Name="PART_Title"
                                    Grid.Column="1"
                                    Margin="6,0"
                                    VerticalAlignment="Center"
                                    FontSize="{DynamicResource WD.TitleFontSize}"
                                    Foreground="{DynamicResource WD.WindowTextBrush}"
                                    Text="{TemplateBinding Title}" />

                                <StackPanel
                                    Grid.Column="2"
                                    Margin="0,6"
                                    shell:WindowChrome.IsHitTestVisibleInChrome="True"
                                    Orientation="Horizontal">

                                    <StackPanel x:Name="PART_MinAndMax" Orientation="Horizontal">
                                        <Button
                                            Name="PART_MinimizeButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.MinimizeWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Minimize], Source={x:Static resx:LanguageManager.Instance}}">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Rectangle
                                                    x:Name="MinimizeGlyph"
                                                    Width="10"
                                                    Height="1"
                                                    Margin="0,7,0,0"
                                                    VerticalAlignment="Bottom"
                                                    Fill="{DynamicResource WD.WindowTextBrush}" />

                                            </Grid>
                                        </Button>
                                        <Button
                                            x:Name="PART_MaximizeButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.MaximizeWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Maximize], Source={x:Static resx:LanguageManager.Instance}}">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Path
                                                    Width="10"
                                                    Height="10"
                                                    HorizontalAlignment="Center"
                                                    VerticalAlignment="Center"
                                                    Data="{DynamicResource WD.WindowMaximizeGeometry}"
                                                    Fill="{DynamicResource WD.WindowTextBrush}"
                                                    Stretch="Uniform"
                                                    UseLayoutRounding="False" />

                                            </Grid>
                                        </Button>
                                        <Button
                                            x:Name="PART_RestoreButton"
                                            Padding="0"
                                            Command="{Binding Source={x:Static shell:SystemCommands.RestoreWindowCommand}}"
                                            IsTabStop="False"
                                            Style="{DynamicResource WD.WindowButtonStyle}"
                                            ToolTip="{Binding [Restore], Source={x:Static resx:LanguageManager.Instance}}"
                                            Visibility="Collapsed">

                                            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                <Path
                                                    Width="10"
                                                    Height="10"
                                                    HorizontalAlignment="Center"
                                                    VerticalAlignment="Center"
                                                    Data="{DynamicResource WD.WindowRestoreGeometry}"
                                                    Fill="{DynamicResource WD.WindowTextBrush}"
                                                    Stretch="Uniform"
                                                    UseLayoutRounding="False" />

                                            </Grid>
                                        </Button>
                                    </StackPanel>

                                    <Button
                                        Name="PART_CloseButton"
                                        Command="{Binding Source={x:Static shell:SystemCommands.CloseWindowCommand}}"
                                        IsTabStop="False"
                                        Style="{DynamicResource WD.WindowButtonStyle}"
                                        ToolTip="{Binding [Close], Source={x:Static resx:LanguageManager.Instance}}">

                                        <Path
                                            Width="10"
                                            Height="10"
                                            HorizontalAlignment="Center"
                                            VerticalAlignment="Center"
                                            Data="{DynamicResource WD.WindowCloseGeometry}"
                                            Fill="{DynamicResource WD.WindowTextBrush}"
                                            Stretch="Uniform" />

                                    </Button>
                                </StackPanel>
                            </Grid>
                            <ContentPresenter
                                x:Name="PART_TitleToolBar"
                                shell:WindowChrome.IsHitTestVisibleInChrome="True"
                                Content="{Binding TitleBar, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                Focusable="False"
                                Visibility="Collapsed" />

                        </control:SmallPanel>
                        <AdornerDecorator Grid.Row="1" KeyboardNavigation.IsTabStop="False">
                            <ContentPresenter x:Name="MainContentPresenter" ClipToBounds="True" />
                        </AdornerDecorator>
                        <ResizeGrip
                            x:Name="ResizeGrip"
                            Grid.Row="1"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Bottom"
                            IsTabStop="False"
                            Visibility="Collapsed" />

                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="TitleBarMode" Value="HighTitleBar">
                        <Setter TargetName="PART_HighTitleBar" Property="Visibility" Value="Visible" />
                        <Setter TargetName="PART_Normal" Property="Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="WindowState" Value="Maximized">
                        <Setter TargetName="PART_RestoreButton" Property="Visibility" Value="Visible" />
                        <Setter TargetName="PART_MaximizeButton" Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="PART_TitleBarRestoreButton" Property="Visibility" Value="Visible" />
                        <Setter TargetName="PART_TitleBarMaximizeButton" Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="PART_Border" Property="Margin" Value="7" />
                    </Trigger>
                    <Trigger Property="WindowStyle" Value="ToolWindow">
                        <Setter TargetName="PART_MinAndMax" Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="PART_TitleBarMinAndMax" Property="Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="NoChrome" Value="True">
                        <Setter TargetName="PART_GridChrome" Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="PART_TitleToolBar" Property="Visibility" Value="Visible" />
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="ResizeMode" Value="CanResizeWithGrip" />
                            <Condition Property="WindowState" Value="Normal" />
                        </MultiTrigger.Conditions>
                        <Setter TargetName="ResizeGrip" Property="Visibility" Value="Visible" />
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
3. 新增 OSVersionHelper.cs
  • 构造函数获取 DPI 信息;
  • IsSnapLayoutSupported() 方法用于判断系统是否支持 Snap 布局;
  • 获取 Windows 版本号;
using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
using System.Windows.Media;
using WPFDevelopers.Helpers;

namespaceWPFDevelopers.Core.Helpers
{
    publicclassOSVersionHelper
    {
        privateconstdouble LogicalDpi = 96.0;
        publicstatic MatrixTransform TransformFromDevice { get; }
        publicstatic MatrixTransform TransformToDevice { get; }

        publicstaticdouble DeviceDpiX { get; }

        publicstaticdouble DeviceDpiY { get; }
        publicstaticdouble DeviceUnitsScalingFactorX => TransformToDevice.Matrix.M11;

        publicstaticdouble DeviceUnitsScalingFactorY => TransformToDevice.Matrix.M22;

        privatestaticbool? _isSnapLayoutSupported; 

        public static bool IsSnapLayoutSupported()
        {
            if (_isSnapLayoutSupported.HasValue)
            {
                return _isSnapLayoutSupported.Value; 
            }

            _isSnapLayoutSupported = CheckSnapLayoutSupport();
            return _isSnapLayoutSupported.Value;
        }


        static OSVersionHelper()
        {
            var dC = Win32.GetDC(IntPtr.Zero);
            if (dC != IntPtr.Zero)
            {
                constint logicPixelsX = 88;
                constint logicPixelsY = 90;
                DeviceDpiX = Win32.GetDeviceCaps(dC, logicPixelsX);
                DeviceDpiY = Win32.GetDeviceCaps(dC, logicPixelsY);
                Win32.ReleaseDC(IntPtr.Zero, dC);
            }
            else
            {
                DeviceDpiX = LogicalDpi;
                DeviceDpiY = LogicalDpi;
            }

            var identity = Matrix.Identity;
            var identity2 = Matrix.Identity;
            identity.Scale(DeviceDpiX / LogicalDpi, DeviceDpiY / LogicalDpi);
            identity2.Scale(LogicalDpi / DeviceDpiX, LogicalDpi / DeviceDpiY);
            TransformFromDevice = new MatrixTransform(identity2);
            TransformFromDevice.Freeze();
            TransformToDevice = new MatrixTransform(identity);
            TransformToDevice.Freeze();
        }

        private static bool CheckSnapLayoutSupport()
        {
            var version = GetWindowsBuildNumber();
            return version >= 22000;
        }

        private static int GetWindowsBuildNumber()
        {
            try
            {
                using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
                {
                    if (key != null)
                    {
                        var buildNumber = key.GetValue("CurrentBuildNumber");
                        if (buildNumber != null && int.TryParse(buildNumber.ToString(), outint result))
                        {
                            return result;
                        }
                    }
                }
            }
            catch
            {
                
            }
            return0;
        }

        #region SnapLayout
        privateconstdouble DPI_SCALE = 1.5;
        publicconstint HTMAXBUTTON = 9;

        private static HwndSource GetWindowHwndSource(DependencyObject dependencyObject)
        {
            if (dependencyObject is Window window)
            {
                return PresentationSource.FromVisual(window) as HwndSource;
            }
            elseif (dependencyObject is ToolTip tooltip)
            {
                return PresentationSource.FromVisual(tooltip) as HwndSource;
            }
            elseif (dependencyObject is Popup popup)
            {
                if (popup.Child isnull)
                    returnnull;

                return PresentationSource.FromVisual(popup.Child) as HwndSource;
            }
            elseif (dependencyObject is Visual visual)
            {
                return PresentationSource.FromVisual(visual) as HwndSource;
            }
            returnnull;
        }
        #endregion
    }
}

图片

GitHub 源码地址[4]

Gitee 源码地址[5]

参考资料
[1] 

原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers

[2] 

码云链接: https://gitee.com/WPFDevelopersOrg/WPFDevelopers

[3] 

Window 控件在 Win11 下不支持 Snap 功能: https://github.com/WPFDevelopersOrg/WPFDevelopers/issues/162

[4] 

GitHub 源码地址: https://github.com/WPFDevelopersOrg/WPFDevelopers/blob/dev/src/WPFDevelopers.Net40/Window.cs

[5] 

Gitee 源码地址: https://gitee.com/WPFDevelopersOrg/WPFDevelopers/blob/dev/src/WPFDevelopers.Net40/Window.cs

【声明】内容源于网络
0
0
Win开发者
Windows开发技术分享与传播。主要涵盖C#/WPF桌面开发、Win32桌面开发、PowerShell、基础逆向技术、Windows高级技巧等。
内容 160
粉丝 0
Win开发者 Windows开发技术分享与传播。主要涵盖C#/WPF桌面开发、Win32桌面开发、PowerShell、基础逆向技术、Windows高级技巧等。
总阅读102
粉丝0
内容160