WPF学习 - 自定义窗体(一)
.Net默认的窗体样式只有四种:None、SingleBorderWindow、ThreeDBorderWindow、ToolWindow,都比较“丑”。而很多时候,我们希望自定义窗体,比如,无边框,有阴影,或者有模糊效果等。
在WPF中,要实现自定义窗体比较简单,主要有两种方法:
1)使用WindowChrome;
2)使用WindowStyle = “None”。
一、使用WindowChrome。
WindowChrome,可以翻译为:窗体装饰条,官方文档中的定义是:表示一个对象,它描述窗口非工作区区域的自定义。(官方链接:WindowChrome 类 (System.Windows.Shell) | Microsoft Learn)
在官方的解释中,窗口由两部分构成:客户区域,非客户区域。
图中,Client Area表示客户区域;其他的部分,统称为非客户区域。
那么WindowChrome的作用是,将客户区域扩展至整个窗体(遮住了非客户区),同时提供部分标准窗体的功能。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <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。如下所示。
1 2 3 4 5 6 7 | <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区域,自然就无法拖动窗体了。
那么如果解决这个问题呢?以及如何添加按钮呢?答案是手动添加标题栏。哈哈~ 如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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后,窗体呈现这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <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> |
这里,你会发现,窗体可以通过鼠标调整大小,但是不能用鼠标拖动。那解决的办法是什么呢?同样是手动设置一个标题栏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 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
三、更多效果
上文只提到了如果去掉边框,同时保留窗体的基本功能。但是窗体的效果似乎没有多么绚烂。那么下一篇文章,咱们就说说如果做效果....
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
2022-09-04 WPF学习 - 自定义Panel