WPF学习 - 自定义窗体(一)
.Net默认的窗体样式只有四种:None、SingleBorderWindow、ThreeDBorderWindow、ToolWindow,都比较“丑”。而很多时候,我们希望自定义窗体,比如,无边框,有阴影,或者有模糊效果等。
在WPF中,要实现自定义窗体比较简单,主要有两种方法:
1)使用WindowChrome;
2)使用WindowStyle = “None”。
一、使用WindowChrome。
WindowChrome,可以翻译为:窗体装饰条,官方文档中的定义是:表示一个对象,它描述窗口非工作区区域的自定义。(官方链接:WindowChrome 类 (System.Windows.Shell) | Microsoft Learn)
在官方的解释中,窗口由两部分构成:客户区域,非客户区域。
图中,Client Area表示客户区域;其他的部分,统称为非客户区域。
那么WindowChrome的作用是,将客户区域扩展至整个窗体(遮住了非客户区),同时提供部分标准窗体的功能。如下所示:
<Window x:Class="ControlTest.WindowNone"
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:ControlTest"
mc:Ignorable="d"
Title="WindowNone" Height="450" Width="800">
<!-- WindowChrome将客户区域扩展至整个窗体,并遮住标题栏、按钮等-->
<WindowChrome.WindowChrome>
<WindowChrome />
</WindowChrome.WindowChrome>
<Grid>
<TabControl>
<TabItem Header="项目"/>
<TabItem Header="代码"/>
</TabControl>
</Grid>
</Window>
备注:这里的边框,是TabControl的边框,不是窗体的边框。
用上WindowChrome后,会惊奇的发现:在原标题栏的位置,可以用鼠标拖动了;在窗体的四周,可以调整窗体的大小了!Amazing!
但同时,又出现了一个新的问题:窗体中的所以内容,都不能交互(鼠标点击,用户输入)了。
这是为什么呢?可以这样理解。WindowChrome就像一个图层,它将窗体整个覆盖住了。因此窗体上的内容,自然就操作不了。那要如何才能点击呢?
这需要给交互控件,添加WindowChrome的附件属性:IsHitTestVisibleInChrome。如下所示。
<Grid>
<!-- 使用WindowChrome的附件属性 -->
<TabControl WindowChrome.IsHitTestVisibleInChrome="True">
<TabItem Header="项目"/>
<TabItem Header="代码"/>
</TabControl>
</Grid>
如果你以为这样就万事大吉了,那只能说太天真了,微软的东西,哪有那么简单的呢??哈哈~
如果真的按照这个代码,你会发现,又不能使用鼠标拖动窗体了。这是为什么呢?明明之前都可以,为何为控件添加了一个附加属性后,就不行了呢?
问题肯定出在WindowChrome上。那么我们再来看看WindowChrome:
图中有颜色的区域,实际上均为透明的,看不见的。此处附上颜色则是为了方便解释。
这个图就是WindowChrome的模型。其中Caption区域,表示标题栏,就是它,允许窗体被鼠标拖动。GlassFrameThickness就是Aero窗体的透明边框(Aero主体只在部分操作系统中支持)。ResizeBorderThickness就是调整窗体大小的边框的粗细,它提供了使用鼠标调整窗体大小的功能。而CornerRadius,则将窗体变成了圆角,它只有在GlassFrameThickness = 0 或者未启用Aero主体的窗口中才有效。。
再回到上面的问题,为什么添加了附加属性,就不能用鼠标拖动窗体了呢?
原因在于,TabControl进入了Caption区域。因为设置了附加属性(IsHitTestVisibleInChrome),表示鼠标可以“击穿”WindowChrome,那么自然就无法“点击”到Caption区域,自然就无法拖动窗体了。
那么如果解决这个问题呢?以及如何添加按钮呢?答案是手动添加标题栏。哈哈~ 如下代码所示:
Xaml代码: <Window x:Class="ControlTest.WindowNone" 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:ControlTest" mc:Ignorable="d" Title="WindowNone" Height="450" Width="800"> <!-- WindowChrome将客户区域扩展至整个窗体,并遮住标题栏、按钮等 --> <WindowChrome.WindowChrome>
<!-- 设置了标题栏的高度 = 30,圆角 = 20 --> <WindowChrome CaptionHeight="30" CornerRadius="20" GlassFrameThickness="0"/> </WindowChrome.WindowChrome> <Border BorderThickness="1"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border Height="30" Background="YellowGreen"> <Grid> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Width" Value="30"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0"/> </Style> </Grid.Resources> <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True"> <Image /> <TextBlock VerticalAlignment="Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True"> <Button Content="_" Click="Btn_Min"/> <Button Content="Max" Click="Btn_Max"/> <Button Content="X" Click="Btn_Close"/> </StackPanel> </Grid> </Border> <!-- 使用WindowChrome的附件属性 --> <TabControl Grid.Row="1" WindowChrome.IsHitTestVisibleInChrome="True"> <TabItem Header="项目"/> <TabItem Header="代码"/> </TabControl> </Grid> </Border> </Window> C# 代码: public partial class WindowNone : Window { public WindowNone() { InitializeComponent(); }
// 最小化 private void Btn_Min(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; }
// 最大化、还原 private void Btn_Max(object sender, RoutedEventArgs e) { if(this.WindowState == WindowState.Normal) { this.WindowState = WindowState.Maximized; } else { this.WindowState = WindowState.Normal; } }
// 关闭窗体 private void Btn_Close(object sender, RoutedEventArgs e) { this.Close(); } }
手动添加了标题栏之后,在标题栏上,你就可以放上任何你放的东西。。。。
二、使用WindowStyle = "None"
将窗体的WindowStyle属性设置为None后,窗体呈现这样:
<Window x:Class="ControlTest.NoneWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="NoneWindow" Height="450" Width="800" WindowStyle="None"> <Grid> <TabControl> <TabItem Header="项目"/> <TabItem Header="代码"/> </TabControl> </Grid> </Window>
这里,你会发现,窗体可以通过鼠标调整大小,但是不能用鼠标拖动。那解决的办法是什么呢?同样是手动设置一个标题栏:
Xaml 代码: <Window x:Class="ControlTest.NoneWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="NoneWindow" Height="450" Width="800" WindowStyle="None" BorderThickness="0" BorderBrush="Transparent"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border Height="30" Background="YellowGreen" MouseDown="TitleMove"> <Grid> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Width" Value="30"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0"/> </Style> </Grid.Resources> <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True"> <Image /> <TextBlock VerticalAlignment="Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True"> <Button Content="_" Click="Btn_Min"/> <Button Content="Max" Click="Btn_Max"/> <Button Content="X" Click="Btn_Close"/> </StackPanel> </Grid> </Border> <TabControl Grid.Row="1" Margin="10"> <TabItem Header="项目"/> <TabItem Header="代码"/> </TabControl> </Grid> </Window> C# 代码: public partial class NoneWindow : Window { public NoneWindow() { InitializeComponent(); } // 窗体移动 private void TitleMove(object sender, MouseButtonEventArgs e) { if (e.ChangedButton != MouseButton.Left) return; // 非左键点击,退出 if (e.ClickCount == 1) { this.DragMove(); // 拖动窗体 } else { WindowMax(); // 双击时,最大化或者还原窗体 } } // 最小化 private void Btn_Min(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } // 关闭窗体 private void Btn_Close(object sender, RoutedEventArgs e) { this.Close(); } // 最大化、还原 private void Btn_Max(object sender, RoutedEventArgs e) { WindowMax(); } private void WindowMax() { if (this.WindowState == WindowState.Normal) { this.WindowState = WindowState.Maximized; } else { this.WindowState = WindowState.Normal; } } }
这种方式下,会发现在窗体的“标题栏”上面,还有一点留白无法去除,同样窗体的边框也是无法去除的。这是为何呢?实际上顶部的留白、以及周变的“边框”,并不是边框。而是ResizeGrip(用于调整窗体大小的控件,它是Window)的样式。当设置Window的ResizeMode = “NoResize”时,这些留白、边框就消失不见了。
此时,又出现了一个问题。没有了这些留白,我如何调整窗体的大小呢?
答案是,可以使用Adorner。因为此处我也是抄了别人的代码,所以就不过多解释了,只把链接放上,各位看官自行查阅吧:https://blog.csdn.net/u013113678/article/details/121548138
三、更多效果
上文只提到了如果去掉边框,同时保留窗体的基本功能。但是窗体的效果似乎没有多么绚烂。那么下一篇文章,咱们就说说如果做效果....