WPF 不深入理解笔记
课件主要来自 WPF入门基础教程合集-bilibili 和 WPF深入讲解合集-bilibili
开篇入门

- 
    每个XAML文档只能有一个顶级元素 
- 
    有命名空间的性质 
- 
    XAML格式区分大小写,但属性类型可以不区分大小写 

创建元素可以在工具箱拖控件到设计窗口,也可以手动写;
双击控件可以快速设置其行为;
WPF的编译过程
.xaml 和 .cs 文件经历如下过程:转换成 .baml 资源(即 .xaml 的二进制形式,解析速度更佳),嵌入到程序集中,应用程序工作时再从程序集读取这些控件信息
这里课件是用 dnSpy 反编译读 .dll 文件得出该结论,还反编译了虎牙直播的图标资源作为示例,看起来不错(?)
布局
小技巧:VS 用 Ctrl+K (先按)和 Ctrl+C (后按)快捷键可以快速注释掉多行代码
常见的布局属性
| 常用属性 | 功能 | 
|---|---|
| HorizontalAlignment | 设置元素水平位置 | 
| VerticalAlignment | 设置元素垂直位置 | 
| Margin | 指定元素与容器的边距 | 
| Height | 指定元素高度 | 
| Width | 指定元素宽度 | 
| WinHeight / WinWidth | 指定元素最小高度和宽度 | 
| MaxHeight / MaxWidth | 指定元素最大高度和宽度 | 
| Padding | 指定元素内部边距 | 
常见的布局容器
| 布局容器 | |
|---|---|
| Grid | 设置框架整体页面布局 可把空间切分成多行多列 | 
| StackPanel | 可用Orientation属性设置元素排列方式,默认是垂直布局 | 
| WrapPanel | 可用Orientation属性设置元素排列方式,默认是水平布局 具备自动换行的特点 | 
| DockPanel | 设置元素的锚定位置 | 
| UniformGrid | 设置元素均匀分布地填充空间 可以显式指定行数列数 | 
例:Grid分隔2行2列表格:
<Grid ShowGridLines="True">
    <Grid.RowDefinitions>
        <RowDefinition> </RowDefinition>
        <RowDefinition> </RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition> </ColumnDefinition>
        <ColumnDefinition> </ColumnDefinition>
    </Grid.ColumnDefinitions>
</Grid>
例:Orientation=”Horizontal”
<StackPanel Orientation="Horizontal">
    <!-- 在未指定时, WrapPanel默认状态是Orientation="Horizontal", 
		 StackPanel默认Orientation="Horizontal"-->
    <Button Width="100" Height="30" Background="Red"></Button>
    <Button Width="100" Height="30" Background="Yellow"></Button>
    <Button Width="100" Height="30" Background="Blue"></Button>
    <Button Width="100" Height="30" Background="Green"></Button>
    <Button Width="100" Height="30" Background="Pink"></Button>
</StackPanel>
例:DockPanel
<DockPanel>
    <Button DockPanel.Dock="Bottom" Width="100" Height="30"></Button>
    <Button DockPanel.Dock="Left" Width="100" Height="30"></Button>
    <Button DockPanel.Dock="Right" Width="100" Height="30"></Button>
    <Button DockPanel.Dock="Top" Width="100" Height="30"></Button>
</DockPanel> 
例:UniformGrid
<UniformGrid Columns="3" Rows="4">
    <Button Width="100" Height="30"></Button>
    <Button Width="100" Height="30"></Button>
    <Button Width="100" Height="30"></Button>
    <Button Width="100" Height="30"></Button>
    <Button Width="100" Height="30"></Button>
</UniformGrid>
控件结构
WPF控件图示
不需要死记硬背所有控件,但需要记住控件所在的命名空间

用Content属性设置更复杂的按钮
转到定义可以看到,Content 方法可以接收更复杂的 object 对象,而 Text 只能接收静态字符串 string
public object Content { get; set; }
// 摘要:
// 获取或设置一个指定如何设置格式的复合字符串System.Windows.Controls.ContentControl.Content 属性,它显示为一个字符串。
//
// 返回结果:
// 指定如何设置格式的复合字符串 System.Windows.Controls.ContentControl.Content 属性,它显示为一个字符串。
    [Bindable(true)]
    [CustomCategoryAttribute("Content")]
public string Text { get; set; }
// 摘要:
// 获取 System.Windows.Documents.TextPointer 到中的内容的末尾 System.Windows.Controls.TextBlock。
//
// 返回结果:
// 一个 System.Windows.Documents.TextPointer 到中的内容的末尾 System.Windows.Controls.TextBlock。
- 
    凡是继承于 ContentControl的控件,定义内容都是用Content;
- 
    除 TextBlock使用的是Text之外,大部分控件都是Content设置其显示内容。
- 
    继承于 Control的大部分控件都具备Padding属性,TextBlock则单独实现了Padding属性。
- 
    Margin是外边距,Padding是内边距
- 
    Content由于是object类型,所以对于常用的Button,CheckBox等类型控件,不仅可以接收字符串类型,也可以接受各种复杂的对象类型
样式
WPF中的各类控件元素都可以自由的设置样式,诸如:字体(FontFamily)、字体大小(FontSize)、背景颜色(Background)、字体颜色(Foreground)、边距(Margin)、水平位置(HorizontalAlignment)、垂直位置(VerticalAlignment)等,而样式是组织和重用的重要工具;
通过 Style 创建一系列封装所有这些细节的样式,就可以避免使用重复的标记填充XAML,此后只需要设置元素的 样式(Style) 属性就能设定其样式;
样式的定义和使用
<Window.Resources>
    <Style x:Key="BaseStyle" TargetType="Button">
        <Setter Property="Width" Value="100"></Setter>
        <Setter Property="Height" Value="40"></Setter>
    </Style>
    <Style x:Key="MyStyle1" TargetType="Button" BasedOn="{StaticResource BaseStyle}">
        <!-- 使 MyStyle1 样式继承 BaseStyle 样式 -->
        <Setter Property="Foreground" Value="Black"> </Setter>
        <Setter Property="Background" Value="Aqua"> </Setter>
        <Setter Property="Content" Value="Style"> </Setter>            
    </Style>
</Window.Resources>
<StackPanel Orientation="Horizontal">
    <Button Style="{StaticResource MyStyle1}"></Button>
    <Button Style="{StaticResource MyStyle1}"></Button>
</StackPanel>
效果:

触发器
当达到了触发的条件的时候,触发器就执行预期内的响应,可以是样式、数据变化、动画等。
触发器通过 Style.Triggers 集合连接到样式中,每个样式都可以有任意多个触发器,并且每个触发器都是 System.Windows.TriggerBase 的派生类实例
触发器的类型
Trigger :监测依赖属性的变化、触发器生效
MultiTrigger :通过多个条件的设置、达到满足条件、触发器生效
DataTrigger :通过数据的变化、触发器生效
MultiDataTrigger  :多个数据条件的触发器
EventTrigger :事件触发器,触发了某类事件时,触发器生效。
例:条件触发-改变按钮样式
<Window.Resources>
    <Style x:Key="BaseStyle" TargetType="Button">
        <Setter Property="Width" Value="100"></Setter>
        <Setter Property="Height" Value="40"></Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver"  Value="True">
                <!-- 单触发 -->
                <Setter Property="Foreground" Value="Blue"></Setter>
                <Setter Property="FontSize" Value="20"></Setter>
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <!-- 多个触发条件 -->
                    <Condition Property="IsMouseOver" Value="True"></Condition>
                    <Condition Property="IsFocused" Value="True"></Condition>
                </MultiTrigger.Conditions>
                <MultiTrigger.Setters>
                    <!-- 生效 -->
                    <Setter Property="Background" Value="Pink"></Setter>
                </MultiTrigger.Setters>
            </MultiTrigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="MyStyle1" TargetType="Button" BasedOn="{StaticResource BaseStyle}">
        <!-- 使 MyStyle1 样式继承 BaseStyle 样式 -->
        <Setter Property="Foreground" Value="Black"> </Setter>
        <Setter Property="Background" Value="Aqua"> </Setter>
        <Setter Property="Content" Value="Style"> </Setter>            
    </Style>
    <Style x:Key="MyStyle2" TargetType="TextBox">
        <Setter Property="Width" Value="100"></Setter>
        <Setter Property="Height" Value="40"></Setter>
        <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Text}" Value="123">
                <!-- 数据触发器,当输入框内容是123时,背景颜色变绿 -->
                <Setter Property="Background" Value="Green"></Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
    
<StackPanel Orientation="Horizontal">
    <Button Style="{StaticResource MyStyle1}"></Button>
    <Button Style="{StaticResource MyStyle1}"></Button>
    <Button Style="{StaticResource MyStyle1}"></Button>
    <TextBox Style="{StaticResource MyStyle2}"></TextBox>
</StackPanel>
效果:

例:设计 MicrosoftToDo 主界面
你已经学了那么多东西了,快来做个 MicrosoftToDo 吧
<Window
    x:Class="MicrosoftToDO.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:MicrosoftToDO"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewmodels="clr-namespace:MicrosoftToDO.ViewModels"
    Width="1000"
    Height="650"
    mc:Ignorable="d">
    <Window.DataContext>
        <viewmodels:MainViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="210" />
            <!-- 左侧状态栏 -->
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <DockPanel LastChildFill="True">
            <TextBlock
                Margin="10,10,0,20"
                DockPanel.Dock="Top"
                FontSize="12"
                Foreground="#FF737373"
                Text="Microsoft To Do" />
            <DockPanel
                Margin="0,10"
                DockPanel.Dock="Top"
                LastChildFill="False">
                <Image
                    Width="30"
                    Height="30"
                    Margin="10,0,0,0"
                    Source="logo.jpg" />
                <TextBlock
                    Margin="10,0,0,0"
                    VerticalAlignment="Center"
                    Text="henji" />
                <TextBlock
                    Margin="0,0,10,0"
                    VerticalAlignment="Center"
                    DockPanel.Dock="Right"
                    FontFamily="./Fonts/#iconfont"
                    FontSize="22"
                    Text="" />
            </DockPanel>
            <ListBox BorderThickness="0" ItemsSource="{Binding MenuItems}">
                <ListBox.ItemContainerStyle>
                    <Style TargetType="ListBoxItem">
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="Height" Value="45" />
                        <Setter Property="Margin" Value="0,2,0,0" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                    <Grid>
                                        <Border x:Name="bd1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                        <Border
                                            x:Name="bd2"
                                            Margin="5,8"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                        <ContentPresenter
                                            HorizontalAlignment="Stretch"
                                            RecognizesAccessKey="True"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                    </Grid>
                                    <ControlTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter TargetName="bd1" Property="Background" Value="#FFF5F4F4" />
                                        </Trigger>
                                        <Trigger Property="IsSelected" Value="True">
                                            <Setter Property="FontWeight" Value="Bold" />
                                            <Setter Property="Foreground" Value="{Binding BackColor}" />
                                            <Setter TargetName="bd1" Property="Opacity" Value="0.1" />
                                            <Setter TargetName="bd1" Property="Background" Value="{Binding BackColor}" />
                                            <Setter TargetName="bd2" Property="BorderThickness" Value="2,0,0,0" />
                                            <Setter TargetName="bd2" Property="BorderBrush" Value="{Binding BackColor}" />
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </ListBox.ItemContainerStyle>
                <!--  listbox的子项数据模板  -->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <DockPanel
                            Margin="10,0"
                            Background="Transparent"
                            LastChildFill="False">
                            <TextBlock Style="{StaticResource iconTextBlockStyle}" Text="{Binding Icon}" />
                            <TextBlock
                                Margin="10,0,0,0"
                                VerticalAlignment="Center"
                                FontSize="14"
                                Text="{Binding Name}" />
                            <TextBlock
                                VerticalAlignment="Center"
                                DockPanel.Dock="Right"
                                FontWeight="Light"
                                Text="{Binding Count}" />
                        </DockPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </DockPanel>
        <Border Grid.Column="1" Background="Green" />
    </Grid>
</Window>
控件模板
控件模板用于来定义控件的外观、样式,还可通过 控件模板的触发器(ControlTemplate.Triggers) 修改控件的行为、响应动画等。
- 在 WPF 中,每个控件都是无外观的,这意味着我们可以完全自定义其可视元素的外观,但是不能修改其内部的行为,因为控件的行为已经被固定在控件的具体类中。
- 在 Winform 中,控件的外观与行为都被固定在控件的具体类中,若想修改按钮的的边框弧度、或者修改控件本身一些细节,必须需要在修改外观的同时,把原来具备的所有行为重写一遍,我们大多数称之为自定义控件。
- 不过这和也没学过 Winform 的我有什么关系呢
模板绑定
相当于是模板定义了一个新类,覆盖了原始的button类;
只有在模板中 xxx =""{TemplateBinding xxx}" 预留了自定义的功能,才允许下面的xaml修改生效;
<!-- .xaml文件 -->
<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="240" Width="404">
    <Window.Resources>
        <Style x:Key="ButtonStyle1" TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border x:Name="border" 
                                CornerRadius="5"
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}">
                            <ContentPresenter x:Name="contentPresenter" Focusable="False"
                                              HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                                              Margin="{TemplateBinding Margin}"
                                              RecognizesAccessKey="True"
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                              VerticalAlignment="{TemplateBinding VerticalAlignment}">
                            </ContentPresenter>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Button Width="100" Height="40" 
                Content="Hello" FontStyle="Italic"
                Style="{StaticResource ButtonStyle1}"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Background="BlueViolet"
                Click="Button_Click">
        </Button>
    </Grid>
</Window>
//.cs文件 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
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 WpfApp1
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("123");
        }
    }
}
效果:

数据模板
<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="240" Width="400">
    <Grid>
        <ComboBox x:Name="com" Width="100" Height="20">
            <ComboBox.ItemTemplate>
                <!-- 很多容器都具备 ItemTemplate -->
                <DataTemplate>
                    <!-- 数据模板,为了使下拉菜单能正确显示 Student 类的姓名和性别 -->
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"></TextBlock>
                        <!-- 绑定 Student 对象的 Name 和 Sex 成员 -->
                        <TextBlock Text="-"></TextBlock>
                        <TextBlock Text="{Binding Sex}"></TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
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 WpfApp1
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            List<Student> array = new List<Student>();
            array.Add(new Student { Name = "张三", Sex = "M" });
            array.Add(new Student { Name = "李四", Sex = "F" });
            array.Add(new Student { Name = "王五", Sex = "NB" });
            com.ItemsSource = array;
        }
    }
    public class Student
    {
        public string Name { get; set; }
        public string Sex { get; set; }
    }
}
效果:

例:DataGrid 实现学生管理数据库的完整项目
这个暂时没整理,回头自己照着抄一遍;
仿写该项目的过程时发现要注意给 DataGrid 设置 AutoGenerateColumns 属性为 “False” ,不然你有几个 public 变量它就给你整出多余的几列;
理解绑定
把某个显示元素和界面其他元素绑定,或者与后台代码相关联;
数据绑定
直接使用 ElementName 属性
<Grid>
    <StackPanel>
        <TextBox x:Name="textblock" Text="我也是一个广door人"></TextBox>
        <TextBox Text="{Binding ElementName=textblock, Path=Text}"></TextBox>
    </StackPanel>
</Grid>    
设置数据的上下文
表示该窗口可以访问 MainViewModel 中的公开属性方法,此处该界面的文本绑定了 MainViewModel 中一个名为 MessageFromMain 的变量
<Windows.DataContext>
    <local: MainViewModel>
</Windows.DataContext>
<Grid>
    <StackPanel>
        <TextBox x:Name="textblock" Text="我也是一个广door人"></TextBox>
        <TextBox Text="{Binding MessageFromMain}"></TextBox>
    </StackPanel>
</Grid>  
