在开发wpf中少不了自定义标题栏的需求,so我整理了我最近几天的成功,分享出来。
首先看看windowchrome的几个参数解释
首先我们需要在 MainWindow 也就是我们的主窗口中的 Window.Resources 中实现 WindowChrome 的基本样式:WindowChrome.ResizeBorderThickness 设置不可见边框宽度WindowChrome.CaptionHeight> 设置属于标题栏的范围——高度WindowChrome.UseAeroCaptionButtons 是否启用默认系统按钮功能——三大金刚键WindowChrome.NonClientFrameEdges 设置客户区域,使用 bottom 可以实现加载时空白窗口而不显示默认窗口,提升用户体验
为了抽象为公用的代码,我们直接使用模版的方式,不需要在 每个window下面去写样式了。
新建Themes/windowControlTemp.xml 文件
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:wbw="clr-namespace:Main.view;assembly=Main"
xmlns:convert="clr-namespace:Main.widget.converter;assembly=Main"
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework">
<convert:WorkAreaWidth x:Key="workAreaWidth" />
<convert:WorkAreaHeight x:Key="workAreaHeight" />
<ControlTemplate x:Key="BaseWindowControlTemplate" TargetType="{x:Type wbw:BaseWindow}">
<Border SnapsToDevicePixels="True" Background="{StaticResource BorderBackgroundColor}" >
<Grid x:Name="RootGrid" Margin="{TemplateBinding Padding}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 标题栏 -->
<Grid Height="{Binding NonClientAreaHeight,Mode=TwoWay}" Background="#4b95f3" x:Name="PART_NonClientArea">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" x:Name="stackPanelTitleLeft">
<Image Name="iconLogo" Source="/Assets/Img/logo.png" Stretch="Uniform" Width="20" Height="20" VerticalAlignment="Center" Margin="5,0,0,0"/>
<TextBlock x:Name="textBlockTitle" Text="{Binding Title,Mode=OneTime}" FontSize="14" VerticalAlignment="Center" Foreground="White" Margin="5,0,0,0" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
Command="SystemCommands.MinimizeWindowCommand"
WindowChrome.IsHitTestVisibleInChrome="True"
Padding="0"
Style="{StaticResource imgButtonStyle}"
x:Name="BtMin">
<Image Margin="8,0" x:Name="iconSmall" Source="/Assets/Img/icon_minimize.png" Stretch="Uniform" Width="18" Height="18"/>
</Button>
<Button
Command="SystemCommands.RestoreWindowCommand"
WindowChrome.IsHitTestVisibleInChrome="True"
Style="{StaticResource imgButtonStyle}"
Padding="0"
x:Name="BtMax" >
<Image Margin="8,0" x:Name="iconMax" Source="/Assets/Img/icon_small.png" Stretch="Uniform" Width="18" Height="18"/>
</Button>
<Button
Command="SystemCommands.MaximizeWindowCommand"
WindowChrome.IsHitTestVisibleInChrome="True"
Style="{StaticResource imgButtonStyle}"
Padding="0"
Visibility="Collapsed"
x:Name="BtMax_min" >
<Image Margin="8,0" x:Name="iconMax_min" Source="/Assets/Img/icon_max.png" Stretch="Uniform" Width="18" Height="18"/>
</Button>
<Button
Command="SystemCommands.CloseWindowCommand"
WindowChrome.IsHitTestVisibleInChrome="True"
Style="{StaticResource imgButtonCloseStyle}"
x:Name="BtClose">
<Image Margin="8,0" x:Name="iconClose" Source="/Assets/Img/icon_close_bar.png" Stretch="Uniform" Width="18" Height="18"/>
</Button>
</StackPanel>
</Grid>
<AdornerDecorator Grid.Row="1" >
<ContentPresenter ClipToBounds="True" />
</AdornerDecorator>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<!--<Setter TargetName="RootGrid" Property="Margin" Value="6" />-->
<Setter Property="Visibility" Value="Visible" TargetName="BtMax" />
<Setter Property="Visibility" Value="Collapsed" TargetName="BtMax_min" />
<Setter TargetName="RootGrid" Property="MaxHeight" Value="{Binding Converter={StaticResource workAreaHeight}}" />
<Setter TargetName="RootGrid" Property="MaxWidth" Value="{Binding Converter={StaticResource workAreaWidth}}" />
</Trigger>
<Trigger Property="WindowState" Value="Normal">
<Setter Property="Visibility" Value="Collapsed" TargetName="BtMax" />
<Setter Property="Visibility" Value="Visible" TargetName="BtMax_min" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="BaseWindowStyle" TargetType="{x:Type wbw:BaseWindow}" >
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="#262e2f" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template" Value="{StaticResource BaseWindowControlTemplate}"/>
</Style>
</ResourceDictionary>
代码中用到了wbw:BaseWindow自定义基类 和 转换器workAreaHeight、workAreaWidth,WindowChrome我是写到基类中了,就没在模板中体现。
workAreaHeight,workAreaWidth 是为了解决最大化的时候边框会溢出问题,所以这里为内容设置最大的高宽来解决此问题,代码如下:
namespace Main.widget.converter
{
public class WorkAreaHeight : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Windows.SystemParameters.WorkArea.Height;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
namespace Main.widget.converter
{
public class WorkAreaWidth : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Windows.SystemParameters.WorkArea.Width;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
BaseWindow基类
namespace Main.view
{
[TemplatePart(Name = ElementNonClientArea, Type = typeof(UIElement))]
public class BaseWindow : System.Windows.Window
{
private const string ElementNonClientArea = "PART_NonClientArea";
//定义一个高宽的属性
public double ContainerWidth { get; set; } = 0;
public double ContainerHeight { get; set; } = 0;
public bool isShowTitleBar { get; set; } = true;
public bool isCloseHint { get; set; } = false;
private Thickness _actualBorderThickness;
public static readonly DependencyProperty NonClientAreaHeightProperty = DependencyProperty.Register(
nameof(NonClientAreaHeight), typeof(double), typeof(BaseWindow),
new PropertyMetadata(35.0));
public double NonClientAreaHeight
{
get => (double)GetValue(NonClientAreaHeightProperty);
set => SetValue(NonClientAreaHeightProperty, value);
}
static BaseWindow(){
StyleProperty.OverrideMetadata(typeof(BaseWindow), new FrameworkPropertyMetadata(ResourceHelper.GetResourceInternal<Style>(“BaseWindowStyle”)));
}
public BaseWindow()
{
InitializeBaseBehavior();
//布局加载完成
this.Loaded += initLoad;
this.Unloaded += BaseWindow_Unloaded;
}
public virtual void InitializeBaseBehavior()
{
var chrome = new WindowChrome
{
UseAeroCaptionButtons = false,//是否启用默认系统按钮功能
ResizeBorderThickness = new Thickness(0, 0, 0, 0),//置不可见边框宽度 拖动大小
NonClientFrameEdges = NonClientFrameEdges.Bottom | NonClientFrameEdges.Left | NonClientFrameEdges.Right,
};
//chrome.GlassFrameThickness = new Thickness(0,0,0,0); //边框玻璃效果 w10下可禁用
//chrome.CornerRadius = new CornerRadius();
WindowChrome.SetResizeGripDirection(this, ResizeGripDirection.None);
//绑定高度值
BindingOperations.SetBinding(chrome, WindowChrome.CaptionHeightProperty,
new Binding(NonClientAreaHeightProperty.Name) { Source = this });
WindowChrome.SetWindowChrome(this, chrome);
//MaxHeight = ScreenUtils.GetRealHeight();
//MaxWidth = ScreenUtils.GetRealWidth();
MinHeight = ScreenUtils.GetRealHeight() * 0.8;
MinWidth = ScreenUtils.GetRealWidth() * 0.8;
this.ResizeMode = System.Windows.ResizeMode.CanResize;
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
WindowState = WindowState.Maximized;
}
//卸载界面完成
private void BaseWindow_Unloaded(object sender, RoutedEventArgs e)
{
}
bool isInitialized = false;
public virtual void initLoad(object sender, RoutedEventArgs re)
{
if (isInitialized) return;
_actualBorderThickness = BorderThickness;
CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand,
(s, e) => WindowState = WindowState.Minimized));
CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand,
(s, e) => WindowState = WindowState.Maximized));
CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand,
(s, e) => WindowState = WindowState.Normal));
CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, (s, e) => btn_close_Click()));
isInitialized = true;
///如果当前窗口的 SizeToContent 模式 不是 WidthAndHeight(即不同时自动调整宽度和高度),则直接退出
if (SizeToContent != SizeToContent.WidthAndHeight)
return;
SizeToContent = SizeToContent.Height;
Dispatcher.BeginInvoke(new Action(() => { SizeToContent = SizeToContent.WidthAndHeight; }));
}
//设置窗口的标题
public void SetTitle(string title)
{
this.Title = title;
}
public void hideTitleBar() {
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize;
Style = null;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
//激活
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
}
//失去焦点
protected override void OnDeactivated(EventArgs e)
{
base.OnDeactivated(e);
}
//关闭阶段
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
}
//关闭完成
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
Dispose();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
}
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
if (SizeToContent == SizeToContent.WidthAndHeight)
InvalidateMeasure();
}
protected override void OnStateChanged(EventArgs e)
{
base.OnStateChanged(e);
Console.WriteLine("OnStateChanged我恢复状态了:" + this.WindowState);
if (WindowState == WindowState.Maximized)
{
BorderThickness = new Thickness();
}
else
{
BorderThickness = _actualBorderThickness;
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
}
public virtual void Dispose()
{
this.Loaded -= initLoad;
this.Unloaded -= BaseWindow_Unloaded;
baseWindowTemplate = null;
}
}
}
其中下面的代码就是加载我们的样式到window样式。
StyleProperty.OverrideMetadata(typeof(BaseWindow), new FrameworkPropertyMetadata(ResourceHelper.GetResourceInternal<Style>(“BaseWindowStyle”)));
helper代码如下:
public class ResourceHelper
{
private static ResourceDictionary _theme;
/// <summary>
/// 获取资源
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static T GetResource<T>(string key)
{
if (Application.Current.TryFindResource(key) is T resource)
{
return resource;
}
return default;
}
internal static T GetResourceInternal<T>(string key)
{
if (GetTheme()[key] is T resource)
{
return resource;
}
return default;
}
/// <summary>
/// get Main skin
/// </summary>
/// <param name="skin"></param>
/// <returns></returns>
public static ResourceDictionary GetSkin(SkinType skin) => new ResourceDictionary()
{
Source = new Uri($"pack://application:,,,/Main;component/Themes/Skin{skin}.xaml")
};
/// <summary>
/// get Main theme
/// </summary>
public static ResourceDictionary GetTheme() => _theme ?? GetStandaloneTheme();
public static ResourceDictionary GetStandaloneTheme()
{
return new ResourceDictionary()
{
Source = new Uri("pack://application:,,,/Main;component/Themes/WindowControlTemp.xaml")
};
}
}
}
使用基类
<local:BaseWindow x:Class="Main.view.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Main.view" mc:Ignorable="d" >
///内容区域
</local:BaseWindow>
这些代码都是项目中的代码,注意代码中的命名空间。
最后看看效果:
最大化:
最小化:
ok,以上就是我的应用的解决方案。