• WPF 入门笔记 - 01 - 入门基础以及常用布局


    🥑本篇为学习博客园大佬圣殿骑士的《WPF基础到企业应用系列》以及部分DotNet菜园的《WPF入门教程系列》所作笔记,对应圣殿骑士《WPF基础到企业应用系列》第 1 - 6 章之间内容,包括 WPF 项目结构、程序的启动和关闭、程序的生命周期、继承关系以及常见的布局控件及其应用。先上链接:

    圣殿骑士博文索引

    DotNet菜园

    WPF核心程序集

    创建一个WPF后,项目引用中会默认引用PresentationCorePresentationFrameworkWindowsBase三个WPF核心程序集:

    image-20230516115833980

    1. PresentationCore:定义了WPF中基本的UI元素和呈现系统的核心功能,包括布局、渲染、输入、事件等。它包含了许多基本的类和接口,如UIElementVisualDispatcherObjectFreezable等。
    2. PresentationFramework:这是WPF中的应用程序框架,提供了一些高级UI控件和应用程序级别的功能,如WindowsPagesNavigationApplicationWindow等。此外,PresentationFramework还定义了WPF的命名空间、样式和主题等。
    3. WindowsBase:它包含了一些基础类和接口,用于支持PresentationCorePresentationFramework,如DependencyObjectDependencyPropertyRoutedEventFrameworkElement等。

    文件结构

    默认生成的文件结构如图:

    image-20230516143847692

    App.xaml 中,可以指定项目运行时启动的窗体,默认是主窗体:MainWindow, 此外还可以还可以定义需要的系统资源以及引入程序集等操作 - App.xaml:

    image-20230519101618613

    MainWindow.xaml中设计主窗体样式:修改标题为:XAMLWithScript,而后添加一个Button按钮,并进行一些对按钮做一些简单的”初始化“ - MainWindow.xaml:

    image-20230519103947108

    当前XAML样式呈现的是一个标题为XAMLWithScript,有一个内容为OnClickButton按钮:

    image-20230519104204005

    样式中定义的事件在当前页面的后台页面MainWindow.xaml.cs中:

    image-20230519104350444

    WPF 和 Winform 案例

    Application

    WPF 和 传统的 WinForm 类似, WPF 同样需要一个 Application 来统领一些全局的行为和操作,并且每个 Domain (应用程序域)中只能有一个 Application 实例存在。和 WinForm 不同的是 WPF Application 默认由两部分组成 : App.xamlApp.xaml.cs,这有点类似于 Delphi Form(我对此只是了解,并没有接触过 Delphi ),将定义和行为代码相分离。当然,这个和 WebForm 也比较类似。XAML 从严格意义上说并不是一个纯粹的 XML 格式文件,它更像是一种 DSL(Domain Specific Language,领域特定语言),它的所有定义都直接映射成某些代码,只是具体的翻译工作交给了编译器完成而已。WPF 应用程序由 System.Windows.Application 类来进行管理。

    WPF 程序启动项

    之前章节有说过WPF程序的启动项默认是通过StartupUri来确定打开哪个窗体,我的理解是它和Winform一样也是通过入口函数来控制打开哪个窗体,只不是默认情况下需要通过StartupUri间接来确定具体打开谁:

    image-20230519113150068

    新建一个类文件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 类的主要职责包括:

    1. 提供应用程序的入口点:Application 类定义了一个静态的 Main() 方法,作为应用程序的入口点。在 Main() 方法中,可以创建一个 Application 对象,并调用 Run() 方法来启动应用程序。
    2. 管理应用程序的生命周期:Application 类负责处理应用程序的启动、关闭和退出过程。它提供了事件和方法,用于在应用程序的不同生命周期阶段执行相应的操作,如 Startup 事件用于处理应用程序启动时的逻辑,Exit 事件用于处理应用程序退出时的逻辑。
    3. 管理应用程序的资源:Application 类允许您定义和访问应用程序级别的资源,如样式、模板、资源字典等。这些资源可以在整个应用程序中共享和重用。
    4. 处理全局异常:Application 类提供了一个 DispatcherUnhandledException 事件,用于捕获和处理应用程序中未处理的异常。您可以订阅该事件,并在发生异常时执行自定义的异常处理逻辑。
    5. 管理应用程序的窗口和导航:Application 类提供了管理应用程序窗口和页面导航的功能。您可以使用 MainWindow 属性设置应用程序的主窗口,使用 NavigationService 属性进行页面之间的导航。

    image-20230519151822700

    WPF 程序关闭

    WPF程序一般通过调用Shutdown()方法关闭程序,当然也可以通过Close()或者Application.Exit()实现。默认WPF项目中,Shutdown()方法是隐式发生的,可以通过在App.xaml中显示调用:

    image-20230519115355970

    ShutdownMode 参数 作用
    OnLastWindowClose(默认值) 最后一个窗体关闭或调用 Application 对象的 Shutdown() 方法时,应用程序关闭。
    OnMainWindowClose 启动窗体关闭或调用 Application 对象的 Shutdown() 方法时,应用程序关闭。(和 C# 的 Windows 应用程序的关闭模式比较类似)
    OnExplicitShutdown 只有在调用 Application 对象的 Shutdown() 方法时,应用程序才会关闭。

    同样你也可以在代码文件(App.xaml.cs)中进行更改,但必须注意这个设置写在app.Run() 方法之前 ,如下代码:

    app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
    app.Run(win);

    Application 对象事件

    image-20230519142432615

    窗体 Window

    窗体均继承自System.Windows.Window基类,前文说过在WPF中,一个窗体通常被分成XAML UI文件和后台.cs代码文件,最早的XAMLWithScript

    // MainWindow.xaml
    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">
    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
    {
    ///
    /// MainWindow.xaml 的交互逻辑
    ///
    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生产环境中放置代码:

    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">
    {
    button1.Content="Hello there.";
    }
    ]]>

    记得把后台.cs文件中关于点击事件注释掉,否则会造成冲突:

    image-20230519145125864

    上述XAML代码在点击按钮后,按钮文本会变成Hello there.:

    image-20230519145231572

    窗体生命周期

    image-20230519150701844

    🥉第一次打开窗口时,只有当引发 Activated 后才会引发 LoadedContentRendered 事件。 记住这一点,在引发 ContentRendered 时,实际上就可认为窗口已打开。

    WPF 窗体的详细的属性、方法、事件请参考 MSDN,有很多的属性、方法、事件与Windows应用程序中System.Windows.Forms.Form类颇为相似,其中常用的一些属性、方法、事件有:

    窗体边框模式(WindowStyle 属性)和是否允许更改窗体大小(ResizeMode 属性)。

    窗体启动位置(WindowStartupLocation 属性)和启动状态(WindowState 属性)等。

    窗体标题(Title 属性)及图标 。

    是否显示在任务栏(ShowInTaskbar

    始终在最前(TopMost 属性)

    Dispatcher 多线程

    微软在WPF引入了Dispatcher,不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。

    Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。Dispatcher本身是一个单例模式,构造函数私有,暴露了一个静态的CurrentDispatcher方法用于获得当前线程的Dispatcher。对于线程来说,它对Dispatcher是一无所知的,Dispatcher内部维护了一个静态的 List _dispatchers, 每当使用CurrentDispatcher方法时,它会在这个_dispatchers中遍历,如果没有找到,则创建一个新的Dispatcher对象,加入到_dispatchers中去。Dispatcher内部维护了一个Thread的属性,创建Dispatcher时会把当前线程赋值给这个 Thread的属性,下次遍历查找的时候就使用这个字段来匹配是否在_dispatchers中已经保存了当前线程的Dispatcher

    继承关系

    image-20230519154334057

    • System.Object 类:基类。
    • System.Windows.Threading.DispatcherObject 类:从图中看WPF 中的使用到的大部分控件与其他类大多是继承 DispatcherObject 类,它提供了用于处理并发和线程的基本构造。
    • System.Windows.DependencyObject类:对WPF中的依赖项属性承载支持与 附加属性承载支持,表示参与 依赖项属性 系统的对象。
    • System.Windows.Media.Visual类:为 WPF 中的呈现提供支持,其中包括命中测试、坐标转换和边界框计算等。
    • System.Windows.UIElement 类:UIElement 是 WPF 核心级实现的基类,该类是 Windows Presentation Foundation (WPF) 中具有可视外观并可以处理基本输入的大多数对象的基类。
    • System.Windows.FrameworkElement类:为 Windows Presentation Foundation (WPF) 元素提供 WPF 框架级属性集、事件集和方法集。此类表示附带的 WPF 框架级实现,它是基于由UIElement定义的 WPF 核心级 API 构建的。
    • System.Windows.Controls.Control 类:表示 用户界面 (UI) 元素的基类,这些元素使用 ControlTemplate 来定义其外观。
    • System.Windows.Controls.ContentControl类:表示没有任何类型的内容表示单个控件。
      • WPF的绝大部分的控件,还包括窗口本身都是继承自ContentControl的:

    image-20230519154643586

    • System.Windows.Controls.ItemsControl 类:表示可用于提供项目的集合的控件。

      image-20230519154822147

    • System.Windows.Controls.Panel类:为所有 Panel 元素提供基类。 使用 Panel 元素定位和排列在 Windows Presentation Foundation (WPF) 应用程序的子对象。

    • System.Windows.Sharps.Sharp类:为 Ellipse、Polygon 和 Rectangle 之类的形状元素提供基类。

    走进Dispatcher

    WPF 线程分配系统提供一个Dispatcher 属性、VerifyAccess CheckAccess方法来操作线程。线程分配系统位于所有WPF类中基类,大部分WPF元素都派生于此类,如下图的Dispatcher类:

    image-20230519151455027

    Dispatcher调度对象想对应的就是DispatcherObject,在WPF中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了Dispatcher的线程 (通常指默认UI线程)才能直接对其进行更新操作。

    image-20230519152039341

    我们声明一个文本Label并尝试在程序运行过程中更新其显示内容:

    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">

    后台代码:

    using System;
    using System.Threading;
    using System.Windows;
    namespace WpfApp2
    {
    ///
    /// MainWindow.xaml 的交互逻辑
    ///
    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:“调用线程无法访问此对象,因为另一个线程拥有该对象。”

    image-20230519152724976

    这和Winform跨线程更新UI是类似的,我们一般会使用委托完成对线程UI的更新。在WPF中,按照DispatcherObject的限制原则,我们改用 Window.Dispatcher.Invoke() 即可顺利完成这个更新操作。

    image-20230519153215035

    如果在其他工程或者类中,我们可以用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很重要,但不能为了UIUI

    WPF 的布局控件都在System.Windows.Controls.Panel这个基类下面,使用Panel元素在WPF应用程序中放置和排列子对象。

    image-20230519155904993

    一个Panel的呈现是测量和排列Children子元素、然后在屏幕上绘制它们的过程。所以在布局的过程中会经过一系列的计算,那么Children越多,执行的计算次数就越多。如果不需要较为复杂的 Panel(如Grid和自定义复杂的 Panel),则可以使用构造相对简单的布局(如 CanvasUniformGrid等),这种布局可带来更好的性能。如果有可能,我们应尽量避免不必要地调用UpdateLayout方法。

    每当Panel内的子元素改变其位置时,布局系统就可能触发一个新的处理过程。对此,了解哪些事件会调用布局系统就很重要,因为不必要的调用可能导致应用程序性能变差。

    换句话说,布局是一个递归系统,实现在屏幕上对元素进行大小调整、定位和绘制,然后进行呈现。具体如下图,要实现控件 0 的布局, 那么先要实现 0 的子控件 01,02... 的布局, 要实现 01 的布局, 那么得实现 01 的子控件 001,002... 的布局, 如此循环直到子控件的布局完成后, 再完成父控件的布局, 最后递归回去直到递归结束, 这样整个布局过程就完成了。

    image-20230519160518655

    布局系统为Panel中的每个子控件完成两个处理过程:测量处理过程(Measure)和排列处理过程(Arrange)。每个子Panel均提供自己的 MeasureOverride ArrangeOverride方法,以实现自己特定的布局行为。

    Canvas

    Canvas是最基本的面板,只是一个存储控件的容器,它不会自动调整内部元素的排列及大小。不指定元素位置,元素将默认显示在画布的左上方。它仅支持用显式坐标定位控件,它也允许指定相对任何角的坐标,而不仅仅是左上角。可以使用Left、Top、Right、 Bottom附加属性在Canvas中定位控件。通过设置LeftRight属性的值表示元素最靠近的那条边,应该与Canvas左边缘或右边缘保持一个固定的距离,设置TopBottom的值也是类似的意思。实质上,你在选择每个控件停靠的角时,附加属性的值是作为外边距使用的。

    Canvas的主要用途是用来画图。Canvas 默认不会自动裁减超过自身范围的内容,即溢出的内容会显示在Canvas外面,这是因为默认 ClipToBounds="False";我们可以通过设置ClipToBounds="True来裁剪多出的内容。

    接下来我们来看两个实例,通过xamlC#实现相同视觉效果:

    image-20230519163254269

    xaml样式:

    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">
    Background="White">
    Canvas.Top="101"
    Width="250"
    Height="200"
    Fill="Blue"
    Stroke="Azure" />
    Canvas.Top="45"
    Width="250"
    Height="100"
    Panel.ZIndex="1"
    Fill="Red"
    Stroke="Green" />

    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属性,那么后者将会被忽略

    StackPanel

    堆栈面板,水平或垂直放置元素。通过设置面板的Orientation属性设置了两种排列方式:横排(Horizontal 默认的)和竖排(Vertical)。纵向的StackPanel默认每个元素宽度与面板一样宽,反之横向亦然。如果包含的元素超过了面板空间,它只会截断多出的内容。 元素的Margin属性用于使元素之间产生一定得间隔,当元素空间大于其内容的空间时,剩余空间将由HorizontalAlignmentVerticalAlignment属性来决定如何分配。

    同样看实例:

    image-20230519170007484
    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">
    Background="White"
    Orientation="Horizontal">
    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

    可换行的行中放置元素,在水平方向上从左向右放置元素,换行后也是从左向右。在垂直方向上,从上到下放置元素,在切换列后也是从上到下。WrapPanel 也提供了Orientation属性设置排列方式,这跟上面的StackPanel基本相似。不同的是WrapPanel会根据内容自动换行。

    ItemHeight - 所有子元素都一致的高度。每个子元素填充高度的方式取决于它的VerticalAlignment属性、Height属性等。任何比ItemHeight高的元素都将被截断。

    ItemWidth - 所有子元素都一致的宽度。每个子元素填充高度的方式取决于它的VerticalAlignment属性、Width属性等。任何比ItemWidth高的元素都将被截断。

    实例:

    image-20230519170838004

    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 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

    停靠面板,根据容器的整个边界调整元素,DockPanel定义一个区域,在此区域中,您可以使子元素通过描点的形式排列,这些对象位于Children属性中。停靠面板其实就是在WinForm类似于Dock属性的元 素。DockPanel会对每个子元素进行排序,并停靠在面板的一侧,多个停靠在同侧的元素则按顺序排序。 

    如果将LastChildFill属性设置为 true(默认设置),那么无论对DockPanel的最后一个子元素设置的其他任何停靠值如何,该子元素都将始终填满剩余的空间。若要将子元素停靠在另一个方向,必须将LastChildFill属性设置为 false,还必须为最后一个子元素指定显式停靠方向。

    默认情况下,面板元素并不接收焦点。要强制使面板元素接收焦点,请将Focusable属性设置为 true

    image-20230519172923789
    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 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

    表格布局,在行列表格中排列元素,它的子控件被放在一个一个实现定义好的小格子里面,整齐配列。

    Grid和其他各个Panel比较起来,功能最多也最为复杂。要使用Grid,首先要向RowDefinitionsColumnDefinitions属性中添加一定数量的RowDefinitionsColumnDefinitions元素,从而定义行数和列数。而放置在Grid面板中的控件元素都必须显示采用附加属性语法定义其放置所在的行和列,它们都是以0为基准的整型值,如果没有显式设置任何行或列,Grid将会隐式地将控件加入在第0行第0列。

    由于Grid的组成并非简单的添加属性标记来区分行列,这也使得用户在实际应用中可以具体到某一单 元格中,所以布局起来就很精细了。

    Grid的列宽与行高可采用固定、自动、按比例三种方式定义

    元素来定义行,其中包含的多个 元素来定义每行的高度。

    • 第一行的高度设置为 "Auto",表示该行的高度会根据其内容自动调整。
    • 第二行的高度也设置为 "Auto",表示该行的高度会根据其内容自动调整。
    • 第三行的高度设置为 "*",表示该行的高度将填充 Grid 的剩余可用空间。
    • 第四行的高度设置为 "40",表示该行的高度固定为 40 个设备无关单位(Device Independent Units)。

    元素来定义列,其中包含多个 元素来定义每列的宽度。

    • 第一列的宽度设置为 "Auto",表示该列的宽度会根据其内容自动调整。
    • 第二列的宽度设置为 "300",表示该列的宽度固定为 300 个设备无关单位。

    上述xaml内容定义了一个四行两列的Grid布局,第一行和第二行的高度根据其内容自动调整,第三行填充剩余的可用空间,第四行的高度固定为 40。第一列的宽度根据其内容自动调整,第二列的宽度固定为 300。

    Grid宽高的几种方式:

    1. 固定大小(Fixed Size): 可以使用具体数值(如像素、设备无关单位等)来指定行高度和列宽度,例如 RowDefinition.Height="100"ColumnDefinition.Width="200"。这样可以使行和列具有固定的大小。
    2. 自动调整(Auto Sizing): 可以使用 Auto 关键字来指定行高度和列宽度,例如 RowDefinition.Height="Auto"ColumnDefinition.Width="Auto"。这样会根据行或列的内容自动调整大小,以适应内容的需求。
    3. 剩余空间填充(Star Sizing): 可以使用 * 关键字来指定行高度和列宽度,例如 RowDefinition.Height="*"ColumnDefinition.Width="*"。这样会使行或列占据剩余可用空间的比例。如果多个行或列都设置为 *,它们将平均分配剩余空间。

    跨越多行多列

    在 Grid 布局中,可以通过合并单元格的方式实现跨越多行和多列的布局效果。这可以通过使用 Grid.RowSpanGrid.ColumnSpan 属性来实现。

    • Grid.RowSpan 属性用于指定一个元素跨越的行数,可以设置为大于 1 的整数值。
    • Grid.ColumnSpan 属性用于指定一个元素跨越的列数,同样可以设置为大于 1 的整数值。

    以下是一个示例,展示了一个元素跨越两行三列的布局:

    Grid.Row="0" Grid.RowSpan="2"
    Grid.Column="0" Grid.ColumnSpan="3" />

    在上述示例中,TextBlock 元素通过设置 Grid.RowSpan="2"Grid.ColumnSpan="3" 属性跨越了两行三列。它位于第一行的第一列,并跨越了第一行、第二行和前三列。其他元素则根据指定的行和列进行定位。

    通过合并单元格的方式,可以创建更复杂的跨越多行多列的布局效果,以满足特定的布局需求。

    分割效果

    GridSplitter 用于在 Grid 布局中创建可调整大小的分割效果。它允许用户通过拖动分隔条来改变相邻行或列的大小。

    以下是 GridSplitter 的基本用法示例:

    在上述示例中,我们创建了一个包含三个列的 Grid 布局。在中间的列,我们添加了一个 GridSplitter 控件,并设置其宽度为 5 个设备无关单位(Device Independent Units)。GridSplitterResizeBehavior 属性设置为 PreviousAndNext,表示它将影响前一个列和后一个列的大小。

    用户可以在运行时通过拖动 GridSplitter 控件来调整左侧和右侧列的宽度。

    GridSplitter 还有其他属性可用于定制其外观和行为,例如 BackgroundResizeDirectionResizeCursor 等。您可以根据需要设置这些属性来满足特定的布局要求。

    请注意,GridSplitter 只能用于 Grid 布局,并且需要适当的行和列定义才能正常工作。确保在使用 GridSplitter 时,考虑布局的其他方面,如最小宽度、最大宽度等,以提供更好的用户体验。

    混合布局效果

    image-20230520111910809

    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">

    添加子元素时不声明具体几行几列时默认都是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属性把网格线显示出来

    UniformGrid

    Grid 简化版,强制所有单元格具有相同尺寸。每个单元格的大小相同,不用在定义行列集合。均布网格每个单元格只能容纳一个元素,将自动按照定义在其内部的元素个数,自动创建行列,并通常保持相同的行列数。UniformGrid 中没有Row Column 附加属性,也没有空白单元格。

    💡与Grid布局控件相比,UniformGrid布局控件很少使用。Grid面板是用于创建简单乃至复杂窗口布局的通用工具。UniformGrid面板是一个一种更特殊的布局容器,主要用于在一个刻板的网格中快速地布局元素。

    image-20230520113240551

    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">

    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 是一个容器控件,允许其内容根据可用空间进行缩放,同时保持其纵横比。它通常用于为其中的内容提供自动缩放和调整大小的行为。

    ViewBox这个控件通常和其他控件结合起来使用,是WPF中非常有用的控件,用来定义一个内容容器。ViewBox组件的作用是拉伸或延展位于其中的组件,以填满可用空间,使之有更好的布局及视觉效果。

    🧨一个 Viewbox中只能放一个控件。如果多添加了一个控件就会报错。

    以下是一些常用的 Viewbox 属性:

    1. Stretch(拉伸):指定内容在视图框中的拉伸方式。常见的取值包括:
      • Uniform(均匀):保持内容的纵横比,同时填充视图框,可能导致内容被裁剪。
      • UniformToFill(均匀填充):保持内容的纵横比,同时填充视图框,可能导致内容被裁剪。
      • Fill(填充):不保持内容的纵横比,将内容拉伸以填充视图框。
    2. StretchDirection(拉伸方向):指定内容在视图框中拉伸的方向。常见的取值包括:
      • Both(双向):内容可以在水平和垂直方向上拉伸。
      • DownOnly(仅向下):内容只能在垂直方向上拉伸。
      • UpOnly(仅向上):内容只能在水平方向上拉伸。
    3. HorizontalAlignment(水平对齐)和 VerticalAlignment(垂直对齐):指定内容在视图框中的水平和垂直对齐方式。常见的取值包括:
      • Left(左对齐):内容在视图框的左侧对齐。
      • Center(居中对齐):内容在视图框的中间对齐。
      • Right(右对齐):内容在视图框的右侧对齐。
      • Top(顶部对齐):内容在视图框的顶部对齐。
      • Bottom(底部对齐):内容在视图框的底部对齐。
    4. MaxWidth(最大宽度)和 MaxHeight(最大高度):指定内容在视图框中的最大宽度和最大高度限制。当内容超过指定的最大尺寸时,将被自动缩放以适应。

    Uniform效果下的Viewbox:

    image-20230520120616348
    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不是布局面板,但是经常与布局类的面板一起配合使用,用于为其内部的内容提供边框和可选的背景样式。它可以包含单个子元素,并且可以根据需要调整其大小以适应子元素。

    以下是 Border 控件的一些常用属性:

    1. Background(背景):指定 Border 的背景颜色或背景画刷。可以使用颜色名称、十六进制值或画刷对象来设置背景。
    2. BorderBrush(边框画刷):指定 Border 的边框颜色或边框画刷。可以使用颜色名称、十六进制值或画刷对象来设置边框。
    3. BorderThickness(边框厚度):指定 Border 的边框厚度。可以设置为单个值,表示统一的边框宽度,或设置为 LeftTopRightBottom 分别指定不同的边框宽度。
    4. CornerRadius(圆角半径):指定 Border 的圆角半径,以使边框的角变得圆润。可以设置为单个值,表示统一的圆角半径,或设置为 TopLeftTopRightBottomRightBottomLeft 分别指定不同的圆角半径。
    5. Padding(内边距):指定 Border 内容与边框之间的空间,也就是内边距。可以设置为单个值,表示统一的内边距,或设置为 LeftTopRightBottom 分别指定不同的内边距。
    6. Child(子元素):指定 Border 的单个子元素。这个子元素将被包含在边框内部,并且可以根据需要调整边框的大小。

    通过使用 Border 控件,您可以为内部内容提供边框和背景样式,并控制边框的大小、边框颜色以及内部内容的对齐和间距。这使得 Border 成为一种常用的容器控件,用于组织和美化界面元素。

    image-20230520144916164
    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">
    Height="250"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Background="LightGray"
    BorderBrush="LightGreen"
    BorderThickness="8"
    CornerRadius="5">
    Canvas.Top="20"
    Width="150"
    Height="150"
    Fill="red"
    Stroke="Black"
    StrokeThickness="10" />
    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

    ScrollViewer 是 WPF 中常用的滚动容器控件,用于在需要时提供滚动功能以显示超出容器可见区域的内容。它可以包含单个子元素,并根据需要在垂直和/或水平方向上显示滚动条。

    以下是 ScrollViewer 控件的一些常用属性:

    1. HorizontalScrollBarVisibility(水平滚动条可见性):指定水平滚动条的可见性。常见的取值包括:
      • Disabled(禁用):禁用水平滚动条。
      • Auto(自动):根据需要自动显示水平滚动条。
      • Visible(可见):始终显示水平滚动条。
    2. VerticalScrollBarVisibility(垂直滚动条可见性):指定垂直滚动条的可见性。常见的取值包括:
      • Disabled(禁用):禁用垂直滚动条。
      • Auto(自动):根据需要自动显示垂直滚动条。
      • Visible(可见):始终显示垂直滚动条。
    3. CanContentScroll(内容滚动):指定 ScrollViewer 是否以逻辑单元(例如行或项)为单位滚动内容。当设置为 True 时,滚动条将以逻辑单元为单位滚动,而不是以像素为单位滚动。
    4. Content(内容):指定 ScrollViewer 的单个子元素。这个子元素将被包含在滚动容器中,并可以根据需要进行滚动。

    通过使用 ScrollViewer 控件,可以将内容放置在可滚动的容器中,以便在容器的可见区域之外显示内容,并通过滚动条来浏览内容。这对于处理大量内容或需要显示大型元素的情况非常有用,以保持界面的可用性和可访问性。

    image-20230520145935943
    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">

    🔊只能放单个子元素

    布局综合应用

    叙利亚战损版 Microsoft ToDo 布局 - 不建议

    image-20230520155138778

    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">
    Orientation="Vertical">

    如果你真的看到了这儿,那我觉得,这件事真是 - 泰裤辣!!!🤔

    自定义Panel

    该章节省略未读。。。

  • 相关阅读:
    尚硅谷-Spring-注解驱动篇
    代码随想录算法训练营15期 Day 2 | 977.有序数组的平方 、209.长度最小的子数组 、59.螺旋矩阵II 、总结
    大数据从入门到精通(超详细版)之Hive案例,指标统计, Sql语句的编写
    moodle4.04无法上传中文文件名
    【错误记录】Uncaught TypeError: m.nodeName.toLowerCase is not a function
    WifiCountryCode&信道
    实用技巧:嵌入式人员使用http服务模拟工具模拟http服务器测试客户端get和post请求
    Java,常用类与API,String类
    招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本
    如何创建一个Web项目
  • 原文地址:https://www.cnblogs.com/BoiledYakult/p/17417386.html