前面写过一篇关于在 WPF 中通过对 WindowChrome 的操作实现自定义窗口并保留一部分的系统功能。
【WPF】WindowChrome 自定义窗口完美实现
有小伙伴看过之后反应,其中有些功能不够完善,本篇来对前面填坑。
CommunityToolkit.Mvvm
Microsoft.Xaml.Behaviors.Wpf
<Brush x:Key="TitleBar">#F0F8FFBrush>
<Brush x:Key="themeColor">#696969Brush>
<Brush x:Key="MouseOverBackground">#87CEEBBrush>
<Brush x:Key="MouseOverForeground">#F0F8FFBrush>
<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 32zGeometry>
<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.7zGeometry>
<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.333334zGeometry>
<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.328619zGeometry>
<ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/icon.icoImageSource>
图片自己换,不在这里提供
主体分为,TitleBar 和 Content 两部分,也就是非用户区和用户区
(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}">
"FontFamily" Value="Microsoft YaHei UI" />
"WindowChrome.WindowChrome" >
"{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
GlassFrameThickness="1"
NonClientFrameEdges="None"
ResizeBorderThickness="5"
UseAeroCaptionButtons="False" />
"Icon" Value="{DynamicResource icon}" />
"Template">
"{x:Type Window}">
x:Name="WindowBorder"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
x:Name="LayoutRoot">
"Auto" />
"*" />
x:Name="WindowTitlePanel"
Grid.Row="0"
Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
Background="{DynamicResource TitleBar}">
"*" />
"Auto" />
"Horizontal">
"{x:Static SystemParameters.SmallIconWidth}"
Height="{x:Static SystemParameters.SmallIconHeight}"
Margin="5,0"
Source="{TemplateBinding Icon}"
WindowChrome.IsHitTestVisibleInChrome="True"/>
"Center"
VerticalAlignment="Center"
Content="{TemplateBinding Title}"
FontSize="{x:Static SystemFonts.CaptionFontSize}"
IsTabStop="False" />
x:Name="WindowCommandButtonsPanel"
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal"
WindowChrome.IsHitTestVisibleInChrome="True">
"1" Background="{TemplateBinding Background}">
"*" />
"28" />
"False">
"Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding Content}"
KeyboardNavigation.TabNavigation="Cycle" />
"1" Background="{DynamicResource themeColor}">
x:Name="ResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
IsTabStop="False"
Visibility="Collapsed" />
Style>
<Style x:Key="WindowTitleBarButtonStyle" TargetType="{x:Type Button}">
"Height" Value="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" />
"Width" Value="40" />
"Background" Value="Transparent" />
"Foreground" Value="{DynamicResource themeColor}" />
"Template">
"{x:Type Button}">
"{TemplateBinding Background}">
"15" Height="15">
"{TemplateBinding Tag}" Fill="{TemplateBinding Foreground}" />
"IsMouseOver" Value="True">
"Background" Value="{DynamicResource MouseOverBackground}" />
"Foreground" Value="{DynamicResource MouseOverForeground}" />
Style>
<Style
x:Key="MinimizeButtonStyle"
BasedOn="{StaticResource WindowTitleBarButtonStyle}"
TargetType="{x:Type Button}">
"Tag" Value="{DynamicResource Minimize}" />
Style>
<Style
x:Key="MaximizeButtonStyle"
BasedOn="{StaticResource WindowTitleBarButtonStyle}"
TargetType="{x:Type Button}">
"Tag" Value="{DynamicResource Maximize}" />
Style>
<Style
x:Key="RestoreButtonStyle"
BasedOn="{StaticResource WindowTitleBarButtonStyle}"
TargetType="{x:Type Button}">
"Tag" Value="{DynamicResource Restore}" />
Style>
<Style
x:Key="CloseButtonStyle"
BasedOn="{StaticResource WindowTitleBarButtonStyle}"
TargetType="{x:Type Button}">
"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>
点击图标显示 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 尽可能的使用 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 对窗口进行高度自定义化,可以最大限度地保留系统功能,比如窗口动画,点击任务栏图标的一些操作等,最主要的我想还是窗口的性能是最接近原生性能的。
这篇文章也只是更加完善了前面文章的不足,后面有更完善的部分,同样会以文章的形式更新出来,供大家参考。