【WPF】填坑 - WindowChrome 自定义窗口完美实现

概述

前面写过一篇关于在 WPF 中通过对 WindowChrome 的操作实现自定义窗口并保留一部分的系统功能。
【WPF】WindowChrome 自定义窗口完美实现
有小伙伴看过之后反应,其中有些功能不够完善,本篇来对前面填坑。

Demo 说明

  • 基于 .net6 WPF MVVM 模式
  • 不在 MainWindow 上进行修改而是新建一个 ShellView 的窗口作为主窗口
  • 必要的 NuGet 包
    • CommunityToolkit.Mvvm
    • Microsoft.Xaml.Behaviors.Wpf

基本样式资源

<!--颜色-->
<Brush x:Key="TitleBar">#F0F8FF</Brush>
<Brush x:Key="themeColor">#696969</Brush>
<Brush x:Key="MouseOverBackground">#87CEEB</Brush>
<Brush x:Key="MouseOverForeground">#F0F8FF</Brush>

<!--字体图标-->
<Geometry x:Key="Minimize">M928.2 548h-832c-17.7 0-32-14.3-32-32s14.3-32 32-32h832c17.7 0 32 14.3 32 32s-14.3 32-32 32z</Geometry>
<Geometry x:Key="Maximize">M812.3 959.4H213.7c-81.6 0-148-66.4-148-148V212.9c0-81.6 66.4-148 148-148h598.5c81.6 0 148 66.4 148 148v598.5C960.3 893 893.9 959.4 812.3 959.4zM213.7 120.9c-50.7 0-92 41.3-92 92v598.5c0 50.7 41.3 92 92 92h598.5c50.7 0 92-41.3 92-92V212.9c0-50.7-41.3-92-92-92H213.7z</Geometry>
<Geometry x:Key="Restore">M714.666667 256H138.666667a53.393333 53.393333 0 0 0-53.333334 53.333333v576a53.393333 53.393333 0 0 0 53.333334 53.333334h576a53.393333 53.393333 0 0 0 53.333333-53.333334V309.333333a53.393333 53.393333 0 0 0-53.333333-53.333333z m10.666666 629.333333a10.666667 10.666667 0 0 1-10.666666 10.666667H138.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V309.333333a10.666667 10.666667 0 0 1 10.666667-10.666666h576a10.666667 10.666667 0 0 1 10.666666 10.666666z m213.333334-746.666666v565.333333a21.333333 21.333333 0 0 1-42.666667 0V138.666667a10.666667 10.666667 0 0 0-10.666667-10.666667H320a21.333333 21.333333 0 0 1 0-42.666667h565.333333a53.393333 53.393333 0 0 1 53.333334 53.333334z</Geometry>
<Geometry x:Key="Close">M952.311261 921.328619 542.892591 510.919389 950.154131 102.671381c8.53028-8.55177 8.53028-22.416546 0-30.967292-8.532327-8.55177-22.360264-8.55177-30.892591 0l-407.262564 408.248008L104.737436 71.704089c-8.53028-8.55177-22.36231-8.55177-30.892591 0-8.529257 8.55177-8.529257 22.416546 0 30.967292l407.262564 408.248008L71.687716 921.328619c-8.529257 8.55177-8.529257 22.416546 0 30.967292 4.26514 4.27435 9.856485 6.41306 15.446807 6.41306 5.590322 0 11.181667-2.13871 15.446807-6.41306l409.41867-410.409231 409.41867 410.409231c4.266164 4.27435 9.855462 6.41306 15.446807 6.41306 5.591345 0 11.17962-2.13871 15.446807-6.41306C960.841541 943.745165 960.841541 929.879366 952.311261 921.328619z</Geometry>
<Geometry x:Key="Success">M512 624a112 112 0 1 0 0-224 112 112 0 0 0 0 224z</Geometry>
<!--图片-->
<ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/icon.ico</ImageSource>

图片自己换,不在这里提供

布局

布局
主体分为,TitleBar 和 Content 两部分,也就是非用户区和用户区

ShellView Style

(SystemParameters.WindowNonClientFrameThickness).Top 获取系统窗口标题栏可触发窗口移动的高度,标题栏上的按钮也通过这里高度
SystemParameters.SmallIconWidth SystemParameters.SmallIconHeight 图标的宽和高,一般为16*16 Pixel
WindowChrome 的处理,这一块相对来说其实比较固定,下面这样处理即可 不保留三大键

<WindowChrome
	CaptionHeight="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
    GlassFrameThickness="1"
    NonClientFrameEdges="None"
    ResizeBorderThickness="5"
    UseAeroCaptionButtons="False" />

通过自定义 Template 实现前面的布局

<Style x:Key="ShellViewStyle" TargetType="{x:Type Window}">
    <Setter Property="FontFamily" Value="Microsoft YaHei UI" />
    <Setter Property="FontSize" Value="14" />
    <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
            <WindowChrome
                CaptionHeight="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                GlassFrameThickness="1"
                NonClientFrameEdges="None"
                ResizeBorderThickness="5"
                UseAeroCaptionButtons="False" />
        </Setter.Value>
    </Setter>
    <!--<Setter Property="Icon" Value="{DynamicResource icon}" />-->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Border
                    x:Name="WindowBorder"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">

                    <Grid x:Name="LayoutRoot">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>

                        <!--#region 标题栏-->
                        <Grid
                            x:Name="WindowTitlePanel"
                            Grid.Row="0"
                            Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                            Background="{DynamicResource TitleBar}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>

                            <!--#region 标题-->
                            <StackPanel Orientation="Horizontal">
                                <Image
                                    Width="{x:Static SystemParameters.SmallIconWidth}"
                                    Height="{x:Static SystemParameters.SmallIconHeight}"
                                    Margin="5,0"
                                    Source="{TemplateBinding Icon}"
                                    WindowChrome.IsHitTestVisibleInChrome="True">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="MouseLeftButtonDown">
                                            <i:InvokeCommandAction Command="{Binding MouseLeftSystemMenuCommand}" />
                                        </i:EventTrigger>
                                        <i:EventTrigger EventName="MouseRightButtonDown">
                                            <i:InvokeCommandAction Command="{Binding MouseRightSystemMenuCommand}" PassEventArgsToCommand="True" />
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                </Image>
                                <ContentControl
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Content="{TemplateBinding Title}"
                                    FontSize="{x:Static SystemFonts.CaptionFontSize}"
                                    IsTabStop="False" />
                            </StackPanel>
                            <!--#endregion 标题-->

                            <!--#region 系统功能键-->
                            <StackPanel
                                x:Name="WindowCommandButtonsPanel"
                                Grid.Column="1"
                                HorizontalAlignment="Right"
                                Orientation="Horizontal"
                                WindowChrome.IsHitTestVisibleInChrome="True">
                                <Button
                                    x:Name="MinimizeButton"
                                    Command="{Binding MinimizeWindowCommand}"
                                    Style="{DynamicResource MinimizeButtonStyle}" />
                                <Grid Margin="1,0">
                                    <Button
                                        x:Name="RestoreButton"
                                        Command="{Binding RestoreWindowCommand}"
                                        Style="{DynamicResource RestoreButtonStyle}"
                                        Visibility="Collapsed" />
                                    <Button
                                        x:Name="MaximizeButton"
                                        Command="{Binding MaximizeWindowCommand}"
                                        Style="{DynamicResource MaximizeButtonStyle}" />
                                </Grid>
                                <Button
                                    x:Name="CloseButton"
                                    Command="{Binding CloseWindowCommand}"
                                    Style="{DynamicResource CloseButtonStyle}" />
                            </StackPanel>
                            <!--#endregion 系统功能键-->
                        </Grid>
                        <!--#endregion 标题栏-->

                        <!--#region 内容-->
                        <Grid Grid.Row="1" Background="{TemplateBinding Background}">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="28" />
                            </Grid.RowDefinitions>
                            <!--  工作区  -->
                            <AdornerDecorator KeyboardNavigation.IsTabStop="False">
                                <ContentControl
								    HorizontalAlignment="Stretch"
								    VerticalAlignment="Stretch"
								    Content="{TemplateBinding Content}"
								    KeyboardNavigation.TabNavigation="Cycle" />
                            </AdornerDecorator>

                            <!--  状态栏  -->
                            <Grid Grid.Row="1" Background="{DynamicResource themeColor}">
                                <StackPanel Orientation="Horizontal">
                                    <Path
                                        Width="10"
                                        Height="10"
                                        Margin="10,0"
                                        Data="{DynamicResource Success}"
                                        Fill="LawnGreen"
                                        Stretch="UniformToFill" />
                                </StackPanel>
                                <ResizeGrip
                                    x:Name="ResizeGrip"
                                    HorizontalAlignment="Right"
                                    VerticalAlignment="Bottom"
                                    IsTabStop="False"
                                    Visibility="Collapsed" />
                            </Grid>
                        </Grid>
                        <!--#endregion 内容-->
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="WindowState" Value="Maximized">
                        <Setter TargetName="LayoutRoot" Property="Margin" Value="8" />
                        <Setter TargetName="RestoreButton" Property="Visibility" Value="Visible" />
                        <Setter TargetName="MaximizeButton" Property="Visibility" Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="WindowState" Value="Normal">
                        <Setter TargetName="ResizeGrip" Property="Visibility" Value="Visible" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

最小化、最大、还原、关闭 按钮

<Style x:Key="WindowTitleBarButtonStyle" TargetType="{x:Type Button}">
    <Setter Property="Height" Value="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" />
    <Setter Property="Width" Value="40" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Foreground" Value="{DynamicResource themeColor}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid Background="{TemplateBinding Background}">
                    <Viewbox Width="15" Height="15">
                        <Path Data="{TemplateBinding Tag}" Fill="{TemplateBinding Foreground}" />
                    </Viewbox>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="{DynamicResource MouseOverBackground}" />
                        <Setter Property="Foreground" Value="{DynamicResource MouseOverForeground}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style
    x:Key="MinimizeButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    <Setter Property="Tag" Value="{DynamicResource Minimize}" />
</Style>

<Style
    x:Key="MaximizeButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    <Setter Property="Tag" Value="{DynamicResource Maximize}" />
</Style>

<Style
    x:Key="RestoreButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    <Setter Property="Tag" Value="{DynamicResource Restore}" />
</Style>

<Style
    x:Key="CloseButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    <Setter Property="Tag" Value="{DynamicResource Close}" />
</Style>

界面元素修正

  • 窗口在最大化后会溢出 8 Pixel 的边距因此在最大化是需要处理边距
  • 最大化按钮和还原按钮在 Window 的 WindowState 属性改变时需要做显示/隐藏处理
<ControlTemplate.Triggers>
    <Trigger Property="WindowState" Value="Maximized">
        <Setter TargetName="LayoutRoot" Property="Margin" Value="8" />
        <Setter TargetName="RestoreButton" Property="Visibility" Value="Visible" />
        <Setter TargetName="MaximizeButton" Property="Visibility" Value="Collapsed" />
    </Trigger>
    <Trigger Property="WindowState" Value="Normal">
        <Setter TargetName="ResizeGrip" Property="Visibility" Value="Visible" />
    </Trigger>
</ControlTemplate.Triggers>

Command Binding

点击图标显示 SystemMenu 菜单模态框,通过 Behaviors 将鼠标事件 Binding 到 ViewModel 中的 Command

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonDown">
        <i:InvokeCommandAction Command="{Binding MouseLeftSystemMenuCommand}" />
    </i:EventTrigger>
    <i:EventTrigger EventName="MouseRightButtonDown">
        <i:InvokeCommandAction Command="{Binding MouseRightSystemMenuCommand}" 
rgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

最小化、最大化、还原、关闭按钮 Binding Command

<Button
    x:Name="MinimizeButton"
    Command="{Binding MinimizeWindowCommand}"/>
<Button
    x:Name="RestoreButton"
    Command="{Binding RestoreWindowCommand}"/>
<Button
    x:Name="MaximizeButton"
    Command="{Binding MaximizeWindowCommand}"/>
<Button
    x:Name="CloseButton"
    Command="{Binding CloseWindowCommand}"/>

Command 实现

Command 尽可能的使用 WPF 提供的系统命令

public partial class ShellViewModel : ObservableObject
{
    private readonly Window _window;
    
    public ShellViewModel()
    {
        _window = Application.Current.MainWindow;
    }

    [RelayCommand]
    private void CloseWindow()
    {
        SystemCommands.CloseWindow(_window);

        Application.Current.Shutdown();
    }

    [RelayCommand]
    private void MinimizeWindow() => SystemCommands.MinimizeWindow(_window);

    [RelayCommand]
    private void MaximizeWindow() => SystemCommands.MaximizeWindow(_window);

    [RelayCommand]
    private void RestoreWindow() => SystemCommands.RestoreWindow(_window);

    [RelayCommand]
    private void MouseLeftSystemMenu()
    {
        var pointing = _window.PointToScreen(new Point(0, 0));

        if (_window.WindowState is WindowState.Maximized)
            pointing.X += 10;
        else
            pointing.X += 2;

        pointing.Y += SystemParameters.WindowNonClientFrameThickness.Top + 1;

        SystemCommands.ShowSystemMenu(_window, pointing);
    }

    [RelayCommand]
    private void MouseRightSystemMenu(MouseEventArgs e)
    {
        FrameworkElement? element = e.OriginalSource as FrameworkElement;

        var pointing = _window.PointToScreen(Mouse.GetPosition(element));
        pointing.X += 5;
        pointing.Y += 5;
        SystemCommands.ShowSystemMenu(_window, pointing);
    }
}

效果

效果
效果
效果

通过 WindowChrome 对窗口进行高度自定义化,可以最大限度地保留系统功能,比如窗口动画,点击任务栏图标的一些操作等,最主要的我想还是窗口的性能是最接近原生性能的

这篇文章也只是更加完善了前面文章的不足,后面有更完善的部分,同样会以文章的形式更新出来,供大家参考。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
实现这个功能,你需要按照以下步骤进行操作: 1. 创建一个 WPF 应用程序。 2. 在主窗口中设计一个按钮。 3. 创建一个新的 WPF 窗口用来显示自定义窗口内容。 4. 在按钮的点击事件中,实例化自定义窗口并显示出来。 下面是详细的步骤: 1. 创建一个 WPF 应用程序。 打开 Visual Studio,选择“文件”>“新建”>“项目”,在“新建项目”对话框中选择“WPF 应用程序”。 2. 在主窗口中设计一个按钮。 打开 MainWindow.xaml 文件,在窗口上添加一个按钮: ``` <Button Content="打开自定义窗口" Click="Button_Click"/> ``` 3. 创建一个新的 WPF 窗口用来显示自定义窗口内容。 在“解决方案资源管理器”中右键单击项目,选择“添加”>“新建项”,在“添加新项”对话框中选择“WPF 窗口”,命名为“CustomWindow.xaml”。 在 CustomWindow.xaml 中添加一些内容,如一个文本框: ``` <Grid> <TextBox Text="这是自定义窗口"/> </Grid> ``` 4. 在按钮的点击事件中,实例化自定义窗口并显示出来。 在 MainWindow.xaml.cs 文件中,添加以下代码: ``` private void Button_Click(object sender, RoutedEventArgs e) { CustomWindow customWindow = new CustomWindow(); customWindow.ShowDialog(); } ``` 这个代码在按钮的点击事件中实例化 CustomWindow 窗口,并通过 ShowDialog() 方法显示出来。 现在,你可以运行应用程序并点击按钮,就会弹出自定义窗口了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑夜中的潜行者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值