🥑本篇为学习博客园大佬圣殿骑士的《WPF基础到企业应用系列》以及部分DotNet菜园的《WPF入门教程系列》所作笔记,对应圣殿骑士《WPF基础到企业应用系列》第 1 - 6 章之间内容,包括 WPF 项目结构、程序的启动和关闭、程序的生命周期、继承关系以及常见的布局控件及其应用。先上链接:
圣殿骑士博文索引
DotNet菜园
创建一个WPF
后,项目引用中会默认引用PresentationCore
、PresentationFramework
、WindowsBase
三个WPF
核心程序集:
PresentationCore
:定义了WPF
中基本的UI
元素和呈现系统的核心功能,包括布局、渲染、输入、事件等。它包含了许多基本的类和接口,如UIElement
、Visual
、DispatcherObject
、Freezable
等。PresentationFramework
:这是WPF
中的应用程序框架,提供了一些高级UI
控件和应用程序级别的功能,如Windows
、Pages
、Navigation
、Application
、Window
等。此外,PresentationFramework
还定义了WPF
的命名空间、样式和主题等。WindowsBase
:它包含了一些基础类和接口,用于支持PresentationCore
和PresentationFramework
,如DependencyObject
、DependencyProperty
、RoutedEvent
、FrameworkElement
等。默认生成的文件结构如图:
在 App.xaml
中,可以指定项目运行时启动的窗体,默认是主窗体:MainWindow
, 此外还可以还可以定义需要的系统资源以及引入程序集等操作 - App.xaml
:
在MainWindow.xaml
中设计主窗体样式:修改标题为:XAMLWithScript
,而后添加一个Button
按钮,并进行一些对按钮做一些简单的”初始化“ - MainWindow.xaml
:
当前XAML
样式呈现的是一个标题为XAMLWithScript
,有一个内容为OnClick
的Button
按钮:
样式中定义的事件在当前页面的后台页面MainWindow.xaml.cs
中:
WPF
和 传统的 WinForm
类似, WPF
同样需要一个 Application
来统领一些全局的行为和操作,并且每个 Domain
(应用程序域)中只能有一个 Application
实例存在。和 WinForm
不同的是 WPF Application
默认由两部分组成 : App.xaml
和 App.xaml.cs
,这有点类似于 Delphi Form
(我对此只是了解,并没有接触过 Delphi
),将定义和行为代码相分离。当然,这个和 WebForm
也比较类似。XAML
从严格意义上说并不是一个纯粹的 XML
格式文件,它更像是一种 DSL
(Domain Specific Language,领域特定语言),它的所有定义都直接映射成某些代码,只是具体的翻译工作交给了编译器完成而已。WPF
应用程序由 System.Windows.Application
类来进行管理。
之前章节有说过WPF
程序的启动项默认是通过StartupUri
来确定打开哪个窗体,我的理解是它和Winform
一样也是通过入口函数来控制打开哪个窗体,只不是默认情况下需要通过StartupUri
间接来确定具体打开谁:
新建一个类文件WPFStartupItem.cs
重新定义程序的入口,在项目属性中将启动对象修改为自定义的类:
using System; using System.Windows; namespace WpfApp2 { class WPFStartupItem : Application { [STAThread] static void Main() { // Method 1 : 创建 Application 对象作为程序入口 Application app = new Application(); Window window = new Window(); // 空窗体 仅做说明 app.Run(window); // Method 2 : 指定 Application 对象的 MainWindow 属性,调用无参数的 Run 方法 Window window1 = new Window(); app.MainWindow = window1; window1.Show(); // 必须调用 Show 方法,否则无法显示窗体 app.Run(); // Method 3 : 通过 URL 的方式启动 app.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative); app.Run(); } } }
WPFStartupItem
是一个WPF
应用程序的启动代码示例,演示了三种不同的方式来启动应用程序。
🍍
[STAThread]
是一个线程特性(thread attribute),用于指定应用程序的主线程类型。在Windows应用程序中,特别是涉及到图形用户界面(GUI)的应用程序中,使用了单线程单元 (STA) 模型。STA模型要求应用程序的主线程(也称为消息循环线程)是单线程的,并且使用了单线程单元的COM组件和功能。
在.NET应用程序中,默认情况下,主线程被标记为多线程单元 (MTA) 模型。但是,对于大多数GUI应用程序,特别是WPF和WinForms应用程序,必须将主线程标记为STA模型,以确保与COM组件和其他GUI相关的功能的兼容性。
因此,为了确保应用程序的主线程被标记为STA模型,需要在主线程的入口方法(例如
Main()
方法)前添加[STAThread]
特性。在给定的示例中,
[STAThread]
特性被应用于Main()
方法,用于指定主线程的模型为STA模型,以确保与GUI和COM组件的兼容性。
🍉
Application
类是WPF
应用程序的核心类之一,它继承自System.Windows.Application
。它提供了管理和控制WPF
应用程序的功能。
Application
类的主要职责包括:
- 提供应用程序的入口点:
Application
类定义了一个静态的Main()
方法,作为应用程序的入口点。在Main()
方法中,可以创建一个Application
对象,并调用Run()
方法来启动应用程序。- 管理应用程序的生命周期:
Application
类负责处理应用程序的启动、关闭和退出过程。它提供了事件和方法,用于在应用程序的不同生命周期阶段执行相应的操作,如Startup
事件用于处理应用程序启动时的逻辑,Exit
事件用于处理应用程序退出时的逻辑。- 管理应用程序的资源:
Application
类允许您定义和访问应用程序级别的资源,如样式、模板、资源字典等。这些资源可以在整个应用程序中共享和重用。- 处理全局异常:
Application
类提供了一个DispatcherUnhandledException
事件,用于捕获和处理应用程序中未处理的异常。您可以订阅该事件,并在发生异常时执行自定义的异常处理逻辑。- 管理应用程序的窗口和导航:
Application
类提供了管理应用程序窗口和页面导航的功能。您可以使用MainWindow
属性设置应用程序的主窗口,使用NavigationService
属性进行页面之间的导航。
WPF
程序一般通过调用Shutdown()
方法关闭程序,当然也可以通过Close()
或者Application.Exit()
实现。默认WPF
项目中,Shutdown()
方法是隐式发生的,可以通过在App.xaml
中显示调用:
ShutdownMode 参数 | 作用 |
---|---|
OnLastWindowClose(默认值) | 最后一个窗体关闭或调用 Application 对象的 Shutdown() 方法时,应用程序关闭。 |
OnMainWindowClose | 启动窗体关闭或调用 Application 对象的 Shutdown() 方法时,应用程序关闭。(和 C# 的 Windows 应用程序的关闭模式比较类似) |
OnExplicitShutdown | 只有在调用 Application 对象的 Shutdown() 方法时,应用程序才会关闭。 |
同样你也可以在代码文件(App.xaml.cs
)中进行更改,但必须注意这个设置写在app.Run()
方法之前 ,如下代码:
app.ShutdownMode = ShutdownMode.OnExplicitShutdown; app.Run(win);
窗体均继承自System.Windows.Window
基类,前文说过在WPF
中,一个窗体通常被分成XAML UI
文件和后台.cs
代码文件,最早的XAMLWithScript
:
// MainWindow.xaml <Window x:Class="WpfApp2.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:WpfApp2" mc:Ignorable="d" Title="XAMLWithScript" Height="450" Width="800" Loaded="Window_Loaded"> <Grid> <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button> </Grid> </Window>
using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApp2 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void button1_Click(object sender, RoutedEventArgs e) { } private void Window_Loaded(object sender, RoutedEventArgs e) { } } }
事件等代码逻辑内容是写在后台代码文件中的,也可以通过x:Code
内部XAML
类型在XAML
生产环境中放置代码:
<Window x:Class="WpfApp2.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:WpfApp2" mc:Ignorable="d" Title="XAMLWithScript" Height="450" Width="800" Loaded="Window_Loaded"> <Grid> <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button> <x:Code> <![CDATA[void button1_Click(object sender,System.Windows.RoutedEventArgs e) { button1.Content="Hello there."; } ]]> </x:Code> </Grid> </Window>
记得把后台.cs
文件中关于点击事件注释掉,否则会造成冲突:
上述XAML
代码在点击按钮后,按钮文本会变成Hello there.
:
🥉第一次打开窗口时,只有当引发 Activated 后才会引发 Loaded 和 ContentRendered 事件。 记住这一点,在引发 ContentRendered 时,实际上就可认为窗口已打开。
WPF
窗体的详细的属性、方法、事件请参考 MSDN
,有很多的属性、方法、事件与Windows
应用程序中System.Windows.Forms.Form
类颇为相似,其中常用的一些属性、方法、事件有:
窗体边框模式(WindowStyle
属性)和是否允许更改窗体大小(ResizeMode
属性)。
窗体启动位置(WindowStartupLocation
属性)和启动状态(WindowState
属性)等。
窗体标题(Title
属性)及图标 。
是否显示在任务栏(ShowInTaskbar
)
始终在最前(TopMost
属性)
微软在WPF
引入了Dispatcher
,不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。
Dispatcher
的作用是用于管理线程工作项队列,类似于Win32
中的消息队列,Dispatcher
的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。Dispatcher
本身是一个单例模式,构造函数私有,暴露了一个静态的CurrentDispatcher
方法用于获得当前线程的Dispatcher
。对于线程来说,它对Dispatcher
是一无所知的,Dispatcher
内部维护了一个静态的 List<Dispatcher> _dispatchers
, 每当使用CurrentDispatcher
方法时,它会在这个_dispatchers
中遍历,如果没有找到,则创建一个新的Dispatcher
对象,加入到_dispatchers
中去。Dispatcher
内部维护了一个Thread
的属性,创建Dispatcher
时会把当前线程赋值给这个 Thread
的属性,下次遍历查找的时候就使用这个字段来匹配是否在_dispatchers
中已经保存了当前线程的Dispatcher
。
WPF
的绝大部分的控件,还包括窗口本身都是继承自ContentControl的:System.Windows.Controls.ItemsControl 类:表示可用于提供项目的集合的控件。
System.Windows.Controls.Panel类:为所有 Panel 元素提供基类。 使用 Panel 元素定位和排列在 Windows Presentation Foundation (WPF) 应用程序的子对象。
System.Windows.Sharps.Sharp类:为 Ellipse、Polygon 和 Rectangle 之类的形状元素提供基类。
WPF
线程分配系统提供一个Dispatcher
属性、VerifyAccess
和CheckAccess
方法来操作线程。线程分配系统位于所有WPF
类中基类,大部分WPF
元素都派生于此类,如下图的Dispatcher
类:
与Dispatcher
调度对象想对应的就是DispatcherObject
,在WPF
中绝大部分控件都继承自 DispatcherObject
,甚至包括 Application
。这些继承自 DispatcherObject
的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了Dispatcher
的线程 (通常指默认UI
线程)才能直接对其进行更新操作。
我们声明一个文本Label
并尝试在程序运行过程中更新其显示内容:
<Window x:Class="WpfApp2.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:WpfApp2" mc:Ignorable="d" Title="XAMLWithScript" Height="450" Width="800" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded"> <Grid> <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button> <Label Name="lbl_Hello">Hello World!</Label> </Grid> </Window>
后台代码:
using System; using System.Threading; using System.Windows; namespace WpfApp2 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Thread thread = new Thread(ModifyUI); thread.Start(); } private void button1_Click(object sender, RoutedEventArgs e) { button1.Content = "Hello there."; } private void Window_Loaded(object sender, RoutedEventArgs e) { } private void ModifyUI() { // 模拟一些工作 Thread.Sleep(TimeSpan.FromSeconds(5)); lbl_Hello.Content = "Hello,Dispatcher"; } } }
在程序运行五秒后就会报错,System.InvalidOperationException:“调用线程无法访问此对象,因为另一个线程拥有该对象。”
这和Winform
跨线程更新UI
是类似的,我们一般会使用委托完成对线程UI
的更新。在WPF
中,按照DispatcherObject
的限制原则,我们改用 Window.Dispatcher.Invoke()
即可顺利完成这个更新操作。
如果在其他工程或者类中,我们可以用Application.Current.Dispatcher.Invoke
方法来完成同样的操作,它们都指向UI Thread Dispatcher
这个唯一的对象。Dispatcher
同时还支持BeginInvoke
异步调用,如下代码:
private void btnHello_Click(object sender, RoutedEventArgs e) { new Thread(() => { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { Thread.Sleep(TimeSpan.FromSeconds(5)); this.lblHello.Content = DateTime.Now.ToString(); })); }).Start(); }
虽然UI
很重要,但不能为了UI
而UI
!
WPF
的布局控件都在System.Windows.Controls.Panel
这个基类下面,使用Panel
元素在WPF
应用程序中放置和排列子对象。
一个Panel
的呈现是测量和排列Children
子元素、然后在屏幕上绘制它们的过程。所以在布局的过程中会经过一系列的计算,那么Children
越多,执行的计算次数就越多。如果不需要较为复杂的 Panel
(如Grid
和自定义复杂的 Panel
),则可以使用构造相对简单的布局(如 Canvas
、UniformGrid
等),这种布局可带来更好的性能。如果有可能,我们应尽量避免不必要地调用UpdateLayout
方法。
每当Panel
内的子元素改变其位置时,布局系统就可能触发一个新的处理过程。对此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差。
换句话说,布局是一个递归系统,实现在屏幕上对元素进行大小调整、定位和绘制,然后进行呈现。具体如下图,要实现控件 0 的布局, 那么先要实现 0 的子控件 01,02... 的布局, 要实现 01 的布局, 那么得实现 01 的子控件 001,002... 的布局, 如此循环直到子控件的布局完成后, 再完成父控件的布局, 最后递归回去直到递归结束, 这样整个布局过程就完成了。
布局系统为Panel
中的每个子控件完成两个处理过程:测量处理过程(Measure
)和排列处理过程(Arrange
)。每个子Panel
均提供自己的 MeasureOverride
和ArrangeOverride
方法,以实现自己特定的布局行为。
Canvas
是最基本的面板,只是一个存储控件的容器,它不会自动调整内部元素的排列及大小。不指定元素位置,元素将默认显示在画布的左上方。它仅支持用显式坐标定位控件,它也允许指定相对任何角的坐标,而不仅仅是左上角。可以使用Left、Top、Right、 Bottom
附加属性在Canvas
中定位控件。通过设置Left
和Right
属性的值表示元素最靠近的那条边,应该与Canvas
左边缘或右边缘保持一个固定的距离,设置Top
和Bottom
的值也是类似的意思。实质上,你在选择每个控件停靠的角时,附加属性的值是作为外边距使用的。
Canvas
的主要用途是用来画图。Canvas
默认不会自动裁减超过自身范围的内容,即溢出的内容会显示在Canvas
外面,这是因为默认 ClipToBounds="False";
我们可以通过设置ClipToBounds="True
来裁剪多出的内容。
接下来我们来看两个实例,通过xaml
和C#
实现相同视觉效果:
xaml
样式:
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <Canvas Margin="0,0,0,0" Background="White"> <Rectangle Canvas.Left="210" Canvas.Top="101" Width="250" Height="200" Fill="Blue" Stroke="Azure" /> <Ellipse Canvas.Left="65" Canvas.Top="45" Width="250" Height="100" Panel.ZIndex="1" Fill="Red" Stroke="Green" /> <Button Name="btnByCode" Click="btnByCode_Click">后台代码实现</Button> </Canvas> </Window>
C#
代码实现:
Canvas canv = new Canvas(); //把canv添加为窗体的子控件 Content = canv; canv.Margin = new Thickness(0, 0, 0, 0); canv.Background = new SolidColorBrush(Colors.White); //Rectangle Rectangle r = new Rectangle(); r.Fill = new SolidColorBrush(Colors.Red); r.Stroke = new SolidColorBrush(Colors.Red); r.Width = 200; r.Height = 140; r.SetValue(Canvas.LeftProperty, (double)200); r.SetValue(Canvas.TopProperty, (double)120); canv.Children.Add(r); //Ellipse Ellipse el = new Ellipse(); el.Fill = new SolidColorBrush(Colors.Blue); el.Stroke = new SolidColorBrush(Colors.Blue); el.Width = 240; el.Height = 80; el.SetValue(Canvas.ZIndexProperty, 1); el.SetValue(Canvas.LeftProperty, (double)100); el.SetValue(Canvas.TopProperty, (double)80); canv.Children.Add(el);
🧨Canvas内的子控件不能使用两个以上的Canvas附加属性,如果同时设置Canvas.Left和Canvas.Right属性,那么后者将会被忽略
堆栈面板,水平或垂直放置元素。通过设置面板的Orientation
属性设置了两种排列方式:横排(Horizontal
默认的)和竖排(Vertical
)。纵向的StackPanel
默认每个元素宽度与面板一样宽,反之横向亦然。如果包含的元素超过了面板空间,它只会截断多出的内容。 元素的Margin
属性用于使元素之间产生一定得间隔,当元素空间大于其内容的空间时,剩余空间将由HorizontalAlignment
和VerticalAlignment
属性来决定如何分配。
同样看实例:
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <StackPanel Margin="0,0,0,0" Background="White" Orientation="Horizontal"> <Button Content="竖排第一个按钮" /> <Button Content="竖排第二个按钮" /> <Button Content="竖排第三个按钮" /> <Button Content="竖排第四个按钮" /> </StackPanel> </Window>
StackPanel sp = new StackPanel(); sp.Orientation = Orientation.Vertical; //把sp添加为窗体的子控件 this.Content = sp; sp.Margin = new Thickness(0, 0, 0, 0); sp.Background = new SolidColorBrush(Colors.White); sp.Orientation = Orientation.Horizontal; //Button1 Button b1 = new Button(); b1.Content = "竖排第一个按钮"; sp.Children.Add(b1); //Button2 Button b2 = new Button(); b2.Content = "竖排第二个按钮"; sp.Children.Add(b2); //Button3 Button b3 = new Button(); b3.Content = "竖排第三个按钮"; sp.Children.Add(b3); //Button4 Button b4 = new Button(); b4.Content = "竖排第四个按钮"; sp.Children.Add(b4);
可换行的行中放置元素,在水平方向上从左向右放置元素,换行后也是从左向右。在垂直方向上,从上到下放置元素,在切换列后也是从上到下。WrapPanel
也提供了Orientation
属性设置排列方式,这跟上面的StackPanel
基本相似。不同的是WrapPanel
会根据内容自动换行。
ItemHeight
- 所有子元素都一致的高度。每个子元素填充高度的方式取决于它的VerticalAlignment
属性、Height
属性等。任何比ItemHeight
高的元素都将被截断。
ItemWidth
- 所有子元素都一致的宽度。每个子元素填充高度的方式取决于它的VerticalAlignment
属性、Width
属性等。任何比ItemWidth
高的元素都将被截断。
实例:
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <WrapPanel Margin="0,0,0,0" Background="White"> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" /> </WrapPanel> </Window>
WrapPanel wp = new WrapPanel(); //把wp添加为窗体的子控件 Content = wp; wp.Margin = new Thickness(0, 0, 0, 0); wp.Background = new SolidColorBrush(Colors.White); //遍历增加Rectangles Rectangle r; for (int i = 0; i <= 10; i++) { r = new Rectangle(); r.Fill = new SolidColorBrush(Colors.LightGreen); r.Margin = new Thickness(10, 10, 10, 10); r.Width = 60; r.Height = 60; wp.Children.Add(r); }
停靠面板,根据容器的整个边界调整元素,DockPanel
定义一个区域,在此区域中,您可以使子元素通过描点的形式排列,这些对象位于Children
属性中。停靠面板其实就是在WinForm
类似于Dock
属性的元 素。DockPanel
会对每个子元素进行排序,并停靠在面板的一侧,多个停靠在同侧的元素则按顺序排序。
如果将LastChildFill
属性设置为 true
(默认设置),那么无论对DockPanel
的最后一个子元素设置的其他任何停靠值如何,该子元素都将始终填满剩余的空间。若要将子元素停靠在另一个方向,必须将LastChildFill
属性设置为 false
,还必须为最后一个子元素指定显式停靠方向。
默认情况下,面板元素并不接收焦点。要强制使面板元素接收焦点,请将Focusable
属性设置为 true
。
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <DockPanel Width="Auto" Height="Auto" LastChildFill="False"> <Button Content="1" DockPanel.Dock="Top" /> <Button Width="40" Content="4" DockPanel.Dock="Left" /> <Button Width="40" Content="2" DockPanel.Dock="Right" /> <Button Content="3" DockPanel.Dock="Bottom" /> </DockPanel> </Window>
DockPanel dockPanel = new DockPanel(); dockPanel.Width = double.NaN; // Auto dockPanel.Height = double.NaN; // Auto dockPanel.LastChildFill = false; Button button1 = new Button(); button1.Content = "1"; DockPanel.SetDock(button1, Dock.Top); Button button2 = new Button(); button2.Width = 40; button2.Content = "4"; DockPanel.SetDock(button2, Dock.Left); Button button3 = new Button(); button3.Width = 40; button3.Content = "2"; DockPanel.SetDock(button3, Dock.Right); Button button4 = new Button(); button4.Content = "3"; DockPanel.SetDock(button4, Dock.Bottom); dockPanel.Children.Add(button1); dockPanel.Children.Add(button2); dockPanel.Children.Add(button3); dockPanel.Children.Add(button4); this.Content = dockPanel;
表格布局,在行列表格中排列元素,它的子控件被放在一个一个实现定义好的小格子里面,整齐配列。
Grid
和其他各个Panel
比较起来,功能最多也最为复杂。要使用Grid
,首先要向RowDefinitions
和ColumnDefinitions
属性中添加一定数量的RowDefinitions
和 ColumnDefinitions
元素,从而定义行数和列数。而放置在Grid
面板中的控件元素都必须显示采用附加属性语法定义其放置所在的行和列,它们都是以0为基准的整型值,如果没有显式设置任何行或列,Grid
将会隐式地将控件加入在第0行第0列。
由于Grid
的组成并非简单的添加属性标记来区分行列,这也使得用户在实际应用中可以具体到某一单 元格中,所以布局起来就很精细了。
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="40" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="300" /> </Grid.ColumnDefinitions> </Grid>
<Grid.RowDefinitions>
元素来定义行,其中包含的多个 <RowDefinition>
元素来定义每行的高度。
<Grid.ColumnDefinitions>
元素来定义列,其中包含多个 <ColumnDefinition>
元素来定义每列的宽度。
上述xaml
内容定义了一个四行两列的Grid
布局,第一行和第二行的高度根据其内容自动调整,第三行填充剩余的可用空间,第四行的高度固定为 40。第一列的宽度根据其内容自动调整,第二列的宽度固定为 300。
Grid
宽高的几种方式:
RowDefinition.Height="100"
和 ColumnDefinition.Width="200"
。这样可以使行和列具有固定的大小。Auto
关键字来指定行高度和列宽度,例如 RowDefinition.Height="Auto"
和 ColumnDefinition.Width="Auto"
。这样会根据行或列的内容自动调整大小,以适应内容的需求。*
关键字来指定行高度和列宽度,例如 RowDefinition.Height="*"
和 ColumnDefinition.Width="*"
。这样会使行或列占据剩余可用空间的比例。如果多个行或列都设置为 *
,它们将平均分配剩余空间。在 Grid 布局中,可以通过合并单元格的方式实现跨越多行和多列的布局效果。这可以通过使用 Grid.RowSpan
和 Grid.ColumnSpan
属性来实现。
Grid.RowSpan
属性用于指定一个元素跨越的行数,可以设置为大于 1 的整数值。Grid.ColumnSpan
属性用于指定一个元素跨越的列数,同样可以设置为大于 1 的整数值。以下是一个示例,展示了一个元素跨越两行三列的布局:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- 跨越两行三列的元素 --> <TextBlock Text="Spanning across multiple rows and columns" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="3" /> <!-- 其他元素 --> <TextBlock Text="Row 2, Column 3" Grid.Row="2" Grid.Column="3" /> <TextBlock Text="Row 1, Column 2" Grid.Row="1" Grid.Column="2" /> </Grid>
在上述示例中,TextBlock
元素通过设置 Grid.RowSpan="2"
和 Grid.ColumnSpan="3"
属性跨越了两行三列。它位于第一行的第一列,并跨越了第一行、第二行和前三列。其他元素则根据指定的行和列进行定位。
通过合并单元格的方式,可以创建更复杂的跨越多行多列的布局效果,以满足特定的布局需求。
GridSplitter
用于在 Grid
布局中创建可调整大小的分割效果。它允许用户通过拖动分隔条来改变相邻行或列的大小。
以下是 GridSplitter
的基本用法示例:
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBlock Text="Left" Grid.Column="0" /> <GridSplitter Width="5" Grid.Column="1" ResizeBehavior="PreviousAndNext" /> <TextBlock Text="Right" Grid.Column="2" /> </Grid>
在上述示例中,我们创建了一个包含三个列的 Grid
布局。在中间的列,我们添加了一个 GridSplitter
控件,并设置其宽度为 5 个设备无关单位(Device Independent Units)。GridSplitter
的 ResizeBehavior
属性设置为 PreviousAndNext
,表示它将影响前一个列和后一个列的大小。
用户可以在运行时通过拖动 GridSplitter
控件来调整左侧和右侧列的宽度。
GridSplitter
还有其他属性可用于定制其外观和行为,例如 Background
、ResizeDirection
、ResizeCursor
等。您可以根据需要设置这些属性来满足特定的布局要求。
请注意,GridSplitter
只能用于 Grid
布局,并且需要适当的行和列定义才能正常工作。确保在使用 GridSplitter
时,考虑布局的其他方面,如最小宽度、最大宽度等,以提供更好的用户体验。
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <Grid Width="Auto" Height="Auto" ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition Height="61*"/> <RowDefinition Height="101*"/> <RowDefinition Height="108*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="139"/> <ColumnDefinition Width="184*"/> <ColumnDefinition Width="45*"/> <ColumnDefinition Width="250*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="第一行、第一列,占1列" Background="LightBlue" HorizontalAlignment="Center"/> <Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3" Content="第一行、占3列"/> <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Content="第3行,第1列开始,占4列" /> </Grid> </Window>
添加子元素时不声明具体几行几列时默认都是0;
Grid grid = new Grid(); grid.Width = double.NaN; grid.Height = double.NaN; grid.ShowGridLines = true; grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(61, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(101, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(108, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(139) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(184, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(45, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(250, GridUnitType.Star) }); TextBlock textBlock = new TextBlock(); textBlock.Text = "第一行、第一列,占1列"; textBlock.Background = Brushes.LightBlue; textBlock.HorizontalAlignment = HorizontalAlignment.Center; Grid.SetRow(textBlock, 0); Grid.SetColumn(textBlock, 0); Grid.SetColumnSpan(textBlock, 1); Button button1 = new Button(); button1.Content = "第一行、占3列"; Grid.SetRow(button1, 0); Grid.SetColumn(button1, 1); Grid.SetRowSpan(button1, 2); Grid.SetColumnSpan(button1, 3); Button button2 = new Button(); button2.Content = "第3行,第1列开始,占4列"; Grid.SetRow(button2, 2); Grid.SetColumn(button2, 0); Grid.SetColumnSpan(button2, 4); grid.Children.Add(textBlock); grid.Children.Add(button1); grid.Children.Add(button2); this.Content = grid;
💡设计的时候看不清楚的话可以通过
ShowGridLines
属性把网格线显示出来
Grid 简化版,强制所有单元格具有相同尺寸。每个单元格的大小相同,不用在定义行列集合。均布网格每个单元格只能容纳一个元素,将自动按照定义在其内部的元素个数,自动创建行列,并通常保持相同的行列数。UniformGrid
中没有Row
和Column
附加属性,也没有空白单元格。
💡与
Grid
布局控件相比,UniformGrid
布局控件很少使用。Grid
面板是用于创建简单乃至复杂窗口布局的通用工具。UniformGrid
面板是一个一种更特殊的布局容器,主要用于在一个刻板的网格中快速地布局元素。
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <UniformGrid Columns="2" Rows="2"> <Button>(0,0)</Button> <Button>(0,1)</Button> <Button>(1,0)</Button> <Button>(1,1)</Button> </UniformGrid> </Window>
C#
代码:
Grid grid = new Grid(); grid.Width = double.NaN; grid.Height = double.NaN; grid.ShowGridLines = true; grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(61, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(101, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(108, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(139) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(184, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(45, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(250, GridUnitType.Star) }); TextBlock textBlock = new TextBlock(); textBlock.Text = "第一行、第一列,占1列"; textBlock.Background = Brushes.LightBlue; textBlock.HorizontalAlignment = HorizontalAlignment.Center; Grid.SetRow(textBlock, 0); Grid.SetColumn(textBlock, 0); Grid.SetColumnSpan(textBlock, 1); Button button1 = new Button(); button1.Content = "第一行、占3列"; Grid.SetRow(button1, 0); Grid.SetColumn(button1, 1); Grid.SetRowSpan(button1, 2); Grid.SetColumnSpan(button1, 3); Button button2 = new Button(); button2.Content = "第3行,第1列开始,占4列"; Grid.SetRow(button2, 2); Grid.SetColumn(button2, 0); Grid.SetColumnSpan(button2, 4); grid.Children.Add(textBlock); grid.Children.Add(button1); grid.Children.Add(button2); this.Content = grid;
Viewbox
是一个容器控件,允许其内容根据可用空间进行缩放,同时保持其纵横比。它通常用于为其中的内容提供自动缩放和调整大小的行为。
ViewBox
这个控件通常和其他控件结合起来使用,是WPF
中非常有用的控件,用来定义一个内容容器。ViewBox
组件的作用是拉伸或延展位于其中的组件,以填满可用空间,使之有更好的布局及视觉效果。
🧨一个
Viewbox
中只能放一个控件。如果多添加了一个控件就会报错。
以下是一些常用的 Viewbox
属性:
Stretch
(拉伸):指定内容在视图框中的拉伸方式。常见的取值包括:
Uniform
(均匀):保持内容的纵横比,同时填充视图框,可能导致内容被裁剪。UniformToFill
(均匀填充):保持内容的纵横比,同时填充视图框,可能导致内容被裁剪。Fill
(填充):不保持内容的纵横比,将内容拉伸以填充视图框。StretchDirection
(拉伸方向):指定内容在视图框中拉伸的方向。常见的取值包括:
Both
(双向):内容可以在水平和垂直方向上拉伸。DownOnly
(仅向下):内容只能在垂直方向上拉伸。UpOnly
(仅向上):内容只能在水平方向上拉伸。HorizontalAlignment
(水平对齐)和 VerticalAlignment
(垂直对齐):指定内容在视图框中的水平和垂直对齐方式。常见的取值包括:
Left
(左对齐):内容在视图框的左侧对齐。Center
(居中对齐):内容在视图框的中间对齐。Right
(右对齐):内容在视图框的右侧对齐。Top
(顶部对齐):内容在视图框的顶部对齐。Bottom
(底部对齐):内容在视图框的底部对齐。MaxWidth
(最大宽度)和 MaxHeight
(最大高度):指定内容在视图框中的最大宽度和最大高度限制。当内容超过指定的最大尺寸时,将被自动缩放以适应。Uniform
效果下的Viewbox
:
<Viewbox Stretch="Uniform"> <Button Content="Hello,Knights Warrior" /> </Viewbox>
Viewbox vb = new Viewbox(); vb.Stretch = Stretch.Uniform; //Button1 Button b1 = new Button(); b1.Content = "Hello,Knights Warrior"; vb.Child = b1; this.Content = vb;
Border
不是布局面板,但是经常与布局类的面板一起配合使用,用于为其内部的内容提供边框和可选的背景样式。它可以包含单个子元素,并且可以根据需要调整其大小以适应子元素。
以下是 Border
控件的一些常用属性:
Background
(背景):指定 Border
的背景颜色或背景画刷。可以使用颜色名称、十六进制值或画刷对象来设置背景。BorderBrush
(边框画刷):指定 Border
的边框颜色或边框画刷。可以使用颜色名称、十六进制值或画刷对象来设置边框。BorderThickness
(边框厚度):指定 Border
的边框厚度。可以设置为单个值,表示统一的边框宽度,或设置为 Left
、Top
、Right
、Bottom
分别指定不同的边框宽度。CornerRadius
(圆角半径):指定 Border
的圆角半径,以使边框的角变得圆润。可以设置为单个值,表示统一的圆角半径,或设置为 TopLeft
、TopRight
、BottomRight
、BottomLeft
分别指定不同的圆角半径。Padding
(内边距):指定 Border
内容与边框之间的空间,也就是内边距。可以设置为单个值,表示统一的内边距,或设置为 Left
、Top
、Right
、Bottom
分别指定不同的内边距。Child
(子元素):指定 Border
的单个子元素。这个子元素将被包含在边框内部,并且可以根据需要调整边框的大小。通过使用 Border
控件,您可以为内部内容提供边框和背景样式,并控制边框的大小、边框颜色以及内部内容的对齐和间距。这使得 Border
成为一种常用的容器控件,用于组织和美化界面元素。
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <Border Width="270" Height="250" HorizontalAlignment="Center" VerticalAlignment="Center" Background="LightGray" BorderBrush="LightGreen" BorderThickness="8" CornerRadius="5"> <Canvas Background="LightYellow"> <Rectangle Canvas.Left="30" Canvas.Top="20" Width="150" Height="150" Fill="red" Stroke="Black" StrokeThickness="10" /> </Canvas> </Border> </Window>
Border border = new Border(); border.Width = 270; border.Height = 250; border.HorizontalAlignment = HorizontalAlignment.Center; border.VerticalAlignment = VerticalAlignment.Center; border.Background = Brushes.LightGray; border.BorderBrush = Brushes.LightGreen; border.BorderThickness = new Thickness(8); border.CornerRadius = new CornerRadius(5); Canvas canvas = new Canvas(); canvas.Background = Brushes.LightYellow; Rectangle rectangle = new Rectangle(); rectangle.Width = 150; rectangle.Height = 150; rectangle.Fill = Brushes.Red; rectangle.Stroke = Brushes.Black; rectangle.StrokeThickness = 10; Canvas.SetLeft(rectangle, 30); Canvas.SetTop(rectangle, 20); canvas.Children.Add(rectangle); border.Child = canvas; // Add the Border to the main Window this.Content = border;
ScrollViewer
是 WPF 中常用的滚动容器控件,用于在需要时提供滚动功能以显示超出容器可见区域的内容。它可以包含单个子元素,并根据需要在垂直和/或水平方向上显示滚动条。
以下是 ScrollViewer
控件的一些常用属性:
HorizontalScrollBarVisibility
(水平滚动条可见性):指定水平滚动条的可见性。常见的取值包括:
Disabled
(禁用):禁用水平滚动条。Auto
(自动):根据需要自动显示水平滚动条。Visible
(可见):始终显示水平滚动条。VerticalScrollBarVisibility
(垂直滚动条可见性):指定垂直滚动条的可见性。常见的取值包括:
Disabled
(禁用):禁用垂直滚动条。Auto
(自动):根据需要自动显示垂直滚动条。Visible
(可见):始终显示垂直滚动条。CanContentScroll
(内容滚动):指定 ScrollViewer
是否以逻辑单元(例如行或项)为单位滚动内容。当设置为 True
时,滚动条将以逻辑单元为单位滚动,而不是以像素为单位滚动。Content
(内容):指定 ScrollViewer
的单个子元素。这个子元素将被包含在滚动容器中,并可以根据需要进行滚动。通过使用 ScrollViewer
控件,可以将内容放置在可滚动的容器中,以便在容器的可见区域之外显示内容,并通过滚动条来浏览内容。这对于处理大量内容或需要显示大型元素的情况非常有用,以保持界面的可用性和可访问性。
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="XAMLWithScript" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <Grid> <ScrollViewer> <Rectangle Width="500" Height="500" Fill="Gray"></Rectangle> </ScrollViewer> </Grid> </Window>
🔊只能放单个子元素
叙利亚战损版 Microsoft ToDo 布局 - 不建议
<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="WPFDemo" Width="800" Height="450" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> <Grid Width="Auto" Height="Auto"> <Grid.ColumnDefinitions > <ColumnDefinition Width="2*"/> <ColumnDefinition Width="8*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="6*"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Grid.Column="1" Grid.RowSpan="8" Background="#218868" Orientation="Vertical"> <TextBlock Text=" 我的一天" FontSize="18" FontWeight="Bold" Foreground="White"/> <TextBlock Text=" 5月20日,星期六" FontSize="10" Foreground="White"/> <TextBlock /> <Border BorderThickness="10" CornerRadius="2" BorderBrush="#1e7b5e" Background="#1e7b5e" > <TextBlock Text="+ 添加任务" Foreground="AntiqueWhite" FontSize="13"/> </Border> </StackPanel> <TextBlock Grid.Row="0" Grid.Column="0" Text="Miscrosoft ToDo" FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center"/> <Button Grid.Row="1" Grid.Column="0" Content="我的一天" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/> <Button Grid.Row="2" Grid.Column="0" Content="重要" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/> <Button Grid.Row="3" Grid.Column="0" Content="已计划日程" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/> <Button Grid.Row="4" Grid.Column="0" Content="已分配任务" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/> <Button Grid.Row="5" Grid.Column="0" Content="任务" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"> <Button.Style> <Style TargetType="Button"> <Setter Property="BorderBrush" Value="LightGray"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="5"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Button.Style> </Button> </Grid> </Window>
如果你真的看到了这儿,那我觉得,这件事真是 - 泰裤辣!!!🤔
该章节省略未读。。。