WPF基础知识

0.前言

此文主要介绍WPF的基础知识,文章会直接摘抄一些优秀文章的表达以及示例用于说明,如果侵犯了作者权利,请联系我速删。编写文章的目的在于记录与分享,方便后续重温与掌握。文章中可能会存在一些小问题,还望各位看官不吝指出。

1.XAML

1.1WPF简介

WPF(Windows Presentation Foundation) 是创建桌面客户端应用程序的UI框架。WPF开发平台支持广泛的应用开发功能,包括应用模型、资源、控件、图形、布局、数据绑定、文档和安全性。WPF是.Net的一部分,WPF 使用 XAML(Extensible Application Markup Language)为应用编程提供声明性模型。

1.2XAML

1.定义
可扩展应用程序标记语言 (XAML) 是一种基于 XML 的声明性语言,可用于创建应用程序UI,其广泛用于以下类型的应用程序:

  • Windows Presentation Fundation(WPF)应用
  • 通用Windows平台(UWP)应用
  • Xamarin.Forms应用

2.优势
将界面设计与逻辑编码分离,例如,设计人员可以设计一个UI,然后将 XAML 文件交给开发人员以添加逻辑代码。即使设计人员和开发人员是同一个人(通常如此),程序员也可以将视觉内容保存在 XAML 文件(.xaml)中,而将逻辑代码保存在代码隐藏文件(.cs)中。

3.例子
下面的例子中涉及WPF中的一些概念,如布局控件、内容控件、x:前缀、数据绑定等,这些概念后面都会介绍到,例子用于对 XAML 有个初步的认识。

<Window x:Class="WPFLearn.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:WPFLearn.Services"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="500" Loaded="Window_Loaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="234*"/>
            <RowDefinition Height="134*"/>
            <RowDefinition Height="67*"/>
        </Grid.RowDefinitions>
        <Canvas Grid.Row="0" Margin="0,0,0,67" Grid.RowSpan="3">
            <Ellipse x:Name="EllipseRed" Fill="Red" Width="100" Height="60" Canvas.Left="56" Canvas.Top="98" local:TurnoverManager.Angle="{Binding ElementName=SliderAngle,Path=Value}"/>
            <Rectangle x:Name="RectangleBlue" Fill="Blue" Width="80" Height="80" Canvas.Left="288" Canvas.Top="171" local:TurnoverManager.Angle="45"/>
            <Button x:Name="ButtonWelcome" Content="欢迎光临" Canvas.Left="265" Canvas.Top="48" FontSize="20" local:TurnoverManager.Angle="60"/>
        </Canvas>
        <WrapPanel Grid.Row="2">
            <Label Content="角度大小"/>
            <Slider x:Name="SliderAngle" Minimum="0" Maximum="240" Width="300"/>
        </WrapPanel>
    </Grid>
</Window>

在这里插入图片描述

1.2语法

XAML 语法这一部分基本内容都是摘抄微软官方的WPF中的 XAML 概述,只不过进行了小部分的词语修改以及增加 Demo,降低理解难度。
1.对象元素
对象元素通常声明类型的实例,该类型在将 XAML 用作语言的技术所引用的程序集中定义。指定对象元素标记时,会创建一条指令,指示 XAML 解析器创建基础类型的新实例。每个实例都是在分析和加载 XAML 时通过调用基础类型的无参数构造函数来创建。

<Grid>
  <Button Content="Click Me!"/>
</Grid>

注解: Grid、Button都是对象元素。

2.特性语法(属性)
(1)对象的属性通常可表示为对象元素的特性。
(2)对象的特性语法为:property=“value”。
(3)特性的值始终指定为包含在引号中的字符串。

<Button Background="Blue" Foreground="Red" Content="Click Me!"/>

注解: Background=“Blue”、Foreground=“Red”、Content=“Click Me!” 都是属性的特性语法。

3.属性元素语法
对于对象元素的某些属性,无法使用特性语法,因为无法在特性语法的引号和字符串限制内充分地为属性值提供所必需的对象或信息。 对于这些情况,可以使用另一个语法,即属性元素语法。

<!--使用属性语法代替特性语法-->
<Button>
    <Button.Background>
        <SolidColorBrush Color="Blue"/>
    </Button.Background>
    <Button.Foreground>
        <SolidColorBrush Color="Red"/>
    </Button.Foreground>
    <Button.Content>
        Click
    </Button.Content>
</Button>

<!--使用属性语法创建渐变色-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="Yellow" Offset="0.0" />
        <GradientStop Color="Red" Offset="0.25" />
        <GradientStop Color="Blue" Offset="0.75" />
        <GradientStop Color="LimeGreen" Offset="1.0" />
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>

注解:
1.第一个 Button 使用了属性语法来赋值 Background、Foreground、Content ,最终效果与前一点的示例一样。
2.第二个 Button 使用了属性语法来创建渐变色,由于渐变色对象无法在双引号中被表达,所以只能使用这种方式给属性赋值。
3.如果可以使用特性语法给属性赋值的话,就选择特性语法,这样会使内容简洁、编写高效。

4.集合语法
XAML 语言包含一些优化,可以生成更易于阅读的标记。其中一项优化是:如果某个特定属性采用集合类型,则在标记中声明为该属性值内的子元素的项目将成为集合的一部分。在这种情况下,子对象元素的集合是设置为集合属性的值。

<!--显式声明集合属性-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStopCollection>
            <GradientStop Color="Yellow" Offset="0.0" />
            <GradientStop Color="Red" Offset="0.25" />
            <GradientStop Color="Blue" Offset="0.75" />
            <GradientStop Color="LimeGreen" Offset="1.0" />
        </GradientStopCollection>
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>

<!--没有声明集合属性,效果相同-->
<Grid >
  <Rectangle Width="200" Height="200">
    <Rectangle.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="Yellow" Offset="0.0" />
        <GradientStop Color="Red" Offset="0.25" />
        <GradientStop Color="Blue" Offset="0.75" />
        <GradientStop Color="LimeGreen" Offset="1.0" />
      </LinearGradientBrush>
    </Rectangle.Fill>
  </Rectangle>
</Grid>

注解: 第二个 Rectangle 设置渐变色时,虽然省略了集合元素 GradientStopCollection ,但 XAML 处理器依旧可以处理得到相同的结果。

5.内容属性
XAML 指定了一个语言功能,通过该功能,类可以指定它的有且仅有一个的属性为 XAML 内容属性。该对象元素的子元素用于设置该内容属性的值。换言之,仅对内容属性而言,可以在 XAML 标记中设置该属性时省略属性元素,并在标记中生成更直观的父级/子级形式。

<!--显示声明内容属性-->
<Border>
    <Border.Child>
        <TextBox Width="300"/>
    </Border.Child>
</Border>
<!--没有声明内容属性,效果相同-->
<Border>
    <TextBox Width="300"/>
</Border>

注解: 本质同集合语法类似,都是 XAML 处理器进行了一些优化,兼容支持了多种写法,此处是省略表示内容属性的 Border.Child 元素。

6.文本内容
有少量 XAML 元素可直接将文本作为其内容来处理。若要实现此功能,必须满足以下条件之一:

  • 类必须声明一个内容属性,并且该内容属性必须是可赋值给字符串的类型(该类型可以是Object)。
  • 类型必须声明一个类型转换器,该类型转换器将文本内容用作初始化文本。
  • 类型必须为已知的XAML语言基元。
<Button>Hello</Button>
<Brush>Blue</Brush>

注解: 本质都是 XMAL 处理器的优化处理。

7.特性语法(事件)
特性语法还可用于事件成员,而非属性成员。 在这种情况下,特性的名称为事件的名称。在 XAML 事件的 WPF 实现中,特性的值是实现该事件的委托的处理程序的名称。

<Button Click="Button_Click" >
    Click Me!
</Button>

注解: 上述的 XAML 文件关联的 CS 文件中,定义了名称为 Button_Click 的函数。

8.大小写和空白
一般而言,XAML 区分大小写。XAML 中的值并不总是区分大小写:值是否区分大小写将取决于与采用该值的属性关联的类型转换器行为,或取决于属性值类型。例如,采用 Boolean 类型的属性可以采用 true 或 True 作为等效值,但只是因为将字符串转换为 Boolean 的本机 WPF XAML 分析程序类型转换已经允许将这些值作为等效值。
WPF XAML 处理器和序列化程序将忽略或删除所有无意义的空白,并标准化任何有意义的空白,即 XAML 将空格、换行符和制表符转化为空格,如果它们在一个字符串的任一端连续出现,则保留一个空格。
9.标记扩展
标记扩展是一个 XAML 语言概念。在提供特性语法的值时,用大括号({ 和 })表示标记扩展用法。 此用法指示 XAML 处理器不要像通常那样将特性值视为文本字符串或者可转换为字符串的值。
WPF 应用编程中最常用的标记扩展是 Binding(用于数据绑定表达式)以及资源引用 StaticResource 和 DynamicResource 。通过使用标记扩展,即使属性通常不支持特性语法,也可以使用特性语法为属性提供值。标记扩展经常使用中间表达式类型实现一些功能,例如,引用仅在运行时才存在的值。

<Window x:Class="index.Window1"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="100" Width="300">
    <Window.Resources>
        <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
        <Style TargetType="Border" x:Key="PageBackground">
            <Setter Property="BorderBrush" Value="Blue"/>
            <Setter Property="BorderThickness" Value="5" />
        </Style>
    </Window.Resources>
    <Border Style="{StaticResource PageBackground}">
        <StackPanel>
            <TextBlock Text="Hello" />
        </StackPanel>
    </Border>
</Window>

注解: 以上标记使用特性语法设置 Style 属性的值。Style 属性采用 Style 类的实例,该类在默认情况下无法由特性语法字符串进行实例化。但在本例中,特性引用了特定的标记扩展 StaticResource 。XAML 处理器处理该标记扩展时,将返回在资源字典中实例化的某个样式的引用。

10.根元素和命名空间
一个 XAML 文件只能有一个根元素,以便同时作为格式正确的 XML 文件和有效的 XAML 文件。对于典型 WPF 方案,可使用在 WPF 应用模型中具有突出意义的根元素(例如,页面的 Window 或 Page 、外部字典的 ResourceDictionary 或应用定义的 Application )。
根元素还包含特性 xmlns 和 xmlns:x 。这些特性向 XAML 处理器指示哪些 XAML 命名空间包含标记将其作为元素引用的后备类型的类型定义。 xmlns 特性明确指示默认的 XAML 命名空间。在默认的 XAML 命名空间中,可以不使用前缀指定标记中的对象元素。对于大多数 WPF 应用程序方案以及 SDK 的 WPF 部分中给出的几乎所有示例,默认的 XAML 命名空间均映射到 WPF 命名空间http://schemas.microsoft.com/winfx/2006/xaml/presentation。xmlns:x 特性指示另一个 XAML 命名空间,该命名空间映射 XAML 语言命名空间http://schemas.microsoft.com/winfx/2006/xaml。
只有在每个 XAML 文件的根元素上, xmlns 特性才是绝对必需的。 xmlns 定义将应用于根元素的所有后代元素。 xmlns 属性也允许位于根下的其他元素上,并将应用于定义元素的任何后代元素。 但是,频繁定义或重新定义 XAML 命名空间会导致难以阅读 XAML 标记样式。

<Page x:Class="index.Page1"
	  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"      
 	  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Page1">
</Page>

11.x:前缀
常用的 x: 前缀如下:

前缀作用
x:Key为(或其他框架中的类似字典概念)每个资源设置唯一的键。
x:Class向为XAML 页提供代码隐藏的类指定 CLR 命名空间和类名。必须具有这样一个类才能支持每个 WPF 编程模型的代码隐藏。
x:Name处理对象元素后,为运行时代码中存在的实例指定运行时对象名称。通常,经常为 x:Name 使用 WPF 定义的等效属性。此类属性特定映射到 CLR 后备属性,因此更便于进行应用编程,在应用编程中,经常使用运行时代码从初始化的 XAML 中查找命名元素。 最常见的此类属性是 FrameworkElement.Name 。在特定类型中不支持等效的 WPF 框架级属性时,仍然可以使 x:Name 。
x:Type根据类型名称构造引用。用于指定采用 Type(例如 Style.TargetType)的特性,但属性经常具有本机的字符串到 Type 的转换功能,因此使用 Type 标记扩展用法是可选的。
x:Null在XAML中指定null值。

注解:
1.指定 Name 的两种方式:Name = “buttonName” 和 x:Name = “buttonName” 。
2.指定 Type 的两种方式:TargetType = “{x:Type Button}” 和 TargetType = “Button” 。
3.x:Null 用于显示指定控件样式为空,来避免指定了类型的样式的全局应用。

12.自定义前缀和自定义类型
对于自身的自定义程序集或 PresentationCore、PresentationFramework 和 WindowsBase 的 WPF 核心以外的程序集,可以将该程序集指定为自定义映射的一部分。只要该类型能够正确地实现以支持正在尝试的 XAML 用法,就可以在 XAML 中引用该程序集中的类型。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:NumericUpDownCustomControl;assembly=CustomLibrary"
>
  <StackPanel Name="LayoutRoot">
    <custom:NumericUpDown Name="numericCtrl1" Width="100" Height="60"/>
  </StackPanel>
</Page>

13.附加属性和附加事件
XAML 指定了一个语言功能,该功能允许对任何元素指定某些属性或事件,即使要设置属性或事件的元素的类型定义中不存在该属性或事件。该功能的属性版本称为附加属性,事件版本称为附加事件。从概念上讲,可以将附加属性和附加事件视为可以在任何 XAML 元素/对象实例上设置的全局成员。
附加属性的最常见方案是使子元素向其父元素报告属性值。

<DockPanel Button.Click="DockPanel_Click">
    <Button DockPanel.Dock="Left" Width="100" Height="20">
        I am on the left
    </Button>
    <Button DockPanel.Dock="Right" Width="100" Height="20">
        I am on the right
    </Button>
</DockPanel>

注解:
1.上述中的 DockPanel 元素在自身中给 Button.Click 属性事件赋值,而这个属性是不属于 DockPanel 元素的,这便是附加事件。在父元素中处理子元素的事件也体现了 WPF 的另一个特性,即路由事件。路由事件就是在 WP F中,事件会按照元素树向上或向下传递,直到被设置了已处理的标识符。
2.上述中的 Button 元素在自身中给 DockPanel.Dock 属性赋值,而这个属性是不属于 Button 元素的,这便是附加属性。

2.控件

2.1控件分类

WPF 中所有控件都继承自 Control ,根据用途不同分为四种类型:布局控件(Panel Controls)、内容控件(Content Controls)、条目控件(Items Controls)、特殊控件(Special Controls)。

2.1.1布局控件

WPF 的布局控件都继承于 System.Windows.Controls.Panel ,常用的布局控件有:Canvas、StackPanel、WrapPanel、DockPanel、Grid、ScrollViewer。
1.Canvas
Canvas 是一个类似于坐标系的面板,所有的元素通过设置坐标来决定其在坐标系中的位置.。具体表现为使用 Left、Top、Right、 Bottom 附加属性在 Canvas 中定位控件。
Canvas 默认不会自动裁剪超过自身范围的内容,即溢出的内容会显示在 Canvas 外面,可以设置 ClipToBounds 属性来裁剪多出的内容。

<Canvas Margin="10,10,10,10" Background="White" >
    <Rectangle Name="rect" Canvas.Left="300" Canvas.Top="180" Fill="Black" Stroke="Red"  Width="200" Height="200"/>
    <Ellipse  Name="el" Canvas.Left="160" Canvas.Top="150" Fill="Azure" Stroke="Green" Width="180" Height="180"/>
</Canvas>

2.StackPanel
StackPanel 将控件按照行或列来顺序排列,但不会换行。通过设置面板的 Orientation 属性控制两种排列方式:横排(Horizontal)和竖排(Vertical),默认为竖排(Vertical)。

<StackPanel Margin="10,10,10,10" Background="Azure">
    <Label>A Button Stack</Label>         
    <Button Content="Button 1"></Button>  
    <Button>Button 2</Button> 
    <Button>Button 3</Button>
    <Button Content="Button 4"></Button>
</StackPanel>

3.WrapPanel
WrapPanel 布局面板将各个控件按照一定方向罗列,当长度或高度不够时自动调整进行换行换列。

  • Orientation=“Horizontal” 时各控件从左至右罗列,当面板长度不够时,子控件就会自动换行,继续按照从左至右的顺序排列。
  • Orientation=“Vertical” 时各控件从上至下罗列,当面板高度不够时,子控件就会自动换列,继续按照从上至下的顺序排列。
<WrapPanel Margin="10" Background="Azure">
    <Button VerticalAlignment="Top" Margin="5">
        Top Button
    </Button>
    <Button MinHeight="50">
        Tall Button
    </Button>
    <Button VerticalAlignment="Bottom">
        Bottom Button
    </Button>
    <Button>
        Stretch Button
    </Button>
    <Button VerticalAlignment="Center">
        Center Button
    </Button>
    <Button>
        Next Button
    </Button>
</WrapPanel>

4.DockPanel
DockPanel 支持让元素简单地停靠在整个面板的某一条边上,然后拉伸元素以填满全部宽度或高度。它也支持让一个元素填充其他已停靠元素没有占用的剩余空间。
DockPanel 有一个 Dock 附加属性,因此子元素用4个值来控制她们的停靠:Left、Top、Right、Bottom 。最后的子元素默认填满所有剩余的空间,除非 DockPanel 的 LastChildFill 属性为 false 。

<DockPanel>
    <Button Content="Button左" DockPanel.Dock="Left"/>
    <Button Content="Button右" DockPanel.Dock="Right"/>
    <Button Content="Button上" DockPanel.Dock="Top"/>
    <Button Content="Button下" DockPanel.Dock="Bottom"/>
</DockPanel>

5.Grid
Grid允许我们通过自定义行列来进行布局,这类似于表格。

(1)基本使用

  • 通过定义Grid的RowDifinitions和ColumnDifinitions来实现对于表格行和列的定义。
  • 元素根据附加属性Grid.Row和Grid.Column确定自己的位置。
  • 元素根据附加属性Grid.RowSpan和Grid.ColumnSpan占用多行/多列空间。

(2)定义列宽与行高

  • 固定长度:属性值为确定的数字。
  • 自动长度:属性值为 Auto 。
  • 比例长度:属性值为 (number)* ,当 number 为空时表示占用剩余空间,否则按照不同行/列的 number 值进行比例计算后分配空间。

(3)实现拖动方式更改区域尺寸的功能
使用 GidSplitter 可以实现 Grid 行列尺寸重新分配。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="6"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Content="Button"/>
    <Button Grid.Row="1" Content="Button"/>
    <GridSplitter  Grid.Row="2" HorizontalAlignment="Stretch"/>
    <Button Grid.Row="3" Grid.RowSpan="2" Content="Button"/>
</Grid>

6.ScrollViewer
ScrollViewer 是带有滚动条的面板。在 ScrollViewer 中只能有一个子控件,若要显示多个子控件,需要先将一个附加的 Panel 控件放置在 ScrollViewer 中,然后可以将子控件放置在该控件中。

  • HorizontalScrollBarVisibility 属性设置水平滚动条是否显示,可选值为 Hidde/Visible/Auto ,默认值为 Hidden ,一般选择 Auto 。
  • VerticalScrollBarVisibility 属性设置垂直滚动条是否显示,可选值为 Hidde/Visible/Auto ,默认值为 Visible ,一般选择 Auto 。
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <Button Content="Button" Width="800" Height="800"/>
</ScrollViewer>
2.1.2内容控件

内容控件都继承自 ContentControl ,只能容纳一个其他控件作为其内容(需要容纳多个控件时,使用布局控件存放多个控件),内容控件根据是否带标题加以细分:

  • 不带标题的内容控件:共同父类是 ContentControl ,常见的控件有:Label、Button 和 Tooltip 等。
  • 带标题的内容控件:共同父类是
    HeaderedContentControl (继承自 ContentControl ),常见的控件有:TabItem、GroupBox 和 Expander 等。
2.1.3条目控件

条目控件都继承自 ItemsControl ,可以显示一列数据,条目控件根据是否带标题加以细分:

  • 不带标题的条目控件:共同父类是 ItemsControl ,常见的控件有:ListBox、Menu 和 StatusBar 等。
  • 带标题的条目控件:共同父类是
    HeaderedItemsControl (继承自 ItemsControl ),常见的控件有 MenuItem、TreeViewItem 和 ToolBar等。
2.1.4特殊控件

特殊控件没有直接的共同父类,这类控件相对比较独立,常见的控件有:TextBox、TextBlock、Border 和 Image 等。其中,Border 控件是为了实现类似圆角按钮这样的功能,WPF 中的自带按钮本身没有圆角这一属性。

2.1.5综合示例
<Window x:Class="WPFLearn.Views.Control.ControlDemo"
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:WPFLearn.Views.Control"
mc:Ignorable="d"
Title="ControlDemo"
Height="500" Width="800">
    <StackPanel Margin="5">
        <GroupBox Header="不带标题的内容控件">
            <Label Content="我是Labe控件"/>
        </GroupBox>
        <GroupBox Header="带标题的内容控件" Margin="0,10,0,0">
            <Expander Header="折叠/展开">
                <Label Content="可以被折叠的内容"/>
            </Expander>
        </GroupBox>
        <GroupBox Header="不带标题的条目控件" Margin="0,10,0,0">
            <ListBox>
                <ListBoxItem Content="C++"/>
                <ListBoxItem Content="C#"/>
                <ListBoxItem Content="Java"/>
            </ListBox>
        </GroupBox>
        <GroupBox Header="带标题的条目控件" Margin="0,10,0,0">
            <Menu>
                <MenuItem Header="文件">
                    <MenuItem Header="新建"/>
                    <Separator/>
                    <MenuItem Header="保存"/>
                    <MenuItem Header="另存为"/>
                </MenuItem>
            </Menu>
        </GroupBox>
        <GroupBox Header="特殊控件" Margin="0,10,0,0">
            <StackPanel>
                <TextBox Height="60" BorderBrush="DarkGray"/>
                <Image Source="/uires/icon.png" Stretch="None" HorizontalAlignment="Left"/>
                <Border BorderBrush="DarkOrange" BorderThickness="1" CornerRadius="5" Height="60" HorizontalAlignment="Left" Padding="5">
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Height="30">
                        <Button Width="90">
                            按钮1
                        </Button>
                        <Button Width="90" Margin="10,0,0,0">
                            按钮2
                        </Button>
                        <Button Width="90" Margin="10,0,0,0">
                            按钮3
                        </Button>
                    </StackPanel>
                </Border>
            </StackPanel>
        </GroupBox>
    </StackPanel>
</Window>

2.2控件外观

XAML 中可以通过三种方式更改控件的外观:

  • 为控件的外观属性赋值。
  • 为控件创建 Style 。
  • 为控件新建 ControlTemplate 。
2.2.1更改外观属性值

许多控件具有支持更改控件外观的属性,常见外观属性有:

  • Margin:控件外边距。
  • Padding:控件内边距。
  • Background:背景颜色,颜色的属性值格式支持常见颜色名称和16进制颜色数值。
  • BorderBursh:边框颜色。
  • BorderThickness:边框厚度。
<Button Margin="10" Background="#FF0000" BorderThickness="1"/>
2.2.2创建Style

WPF 支持通过创建样式来指定控件的外观,以实现批量设置实例外观的效果。

  • 任意元素都有 Resources 属性,在该属性元素下可以创建任意可实例化的资源,比如 WPF 程序集中的类型/C#中的基础类型/…
  • 可以在任意 Resources 节点下用 Style 标签创建样式。
  • Style 元素的 TargetType 属性可以用两种方式表达:TargetType = “Button” 和 TargetType = {x:Type Button}。
  • 如果 Style 元素没有显示指定 TargetType ,则在元素中使用属性时需要加上 Control 前缀。
  • 如果 Style 元素只指定 TargetType,而没有指定 x:Key,则样式会自动给样式定义后该类型的所有元素。如果需要取消引用,需要显示指定 Style = {x:Null} 。
  • 通过使用属性 BasedOn 实现样式的继承。
2.2.3新建ControlTemplate

在 WPF 中,控件的 ControlTemplate 用于定义控件的外观。可以通过定义新的 ControlTemplate 并将其分配给控件来更改控件的结构和外观。在许多情况下,模板提供了足够的灵活性,从而无需自行编写自定义控件。实现方式:

  • 通用:在 Resources 节点下定义 ControlTemplate 元素,然后给控件的 Template 属性设置ControlTemplate 元素的 Key 值。
  • 封装:定义样式时,设置 Template 属性,其值用属性元素方式表达 ControlTemplate 元素。
2.3.4综合示例
<Window x:Class="WPFLearn.Views.Control.ControlAppearanceDemo"
        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:WPFLearn.Views.Control"
        mc:Ignorable="d"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="控件外观演示" Height="450" Width="800">
    <Window.Resources>
        <!--###没有x:Key属性的Style将应用到所有类型为TargetType的实例-->
        <!--<Style TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="16"/>
        </Style>-->

        <!--###在Resources节点下创建样式,以实现批量设置效果-->
        <Style x:Key="StyleButton" TargetType="{x:Type Button}">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="16"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="FontWeight" Value="Bold"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="StyleButtonWithControlTemplate" TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                            <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <!--###单独定义ControlTemplate,然后再设置Button.Template属性-->
        <ControlTemplate TargetType="Button" x:Key="ControlTemplateButton">
            <Grid>
                <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>
    <StackPanel>
        <GroupBox Header="更改外观属性值">
            <StackPanel Orientation="Horizontal">
                <StackPanel.Resources>
                    <!--###在任意元素的Resources元素下创建资源,以实现值定义效果-->
                    <FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
                    <sys:Double x:Key="ButtonFontSize">16</sys:Double>
                </StackPanel.Resources>
                <Button FontFamily="Times New Roman" FontSize="16" >
                    StyledButton1
                </Button>
                <Button FontFamily="Times New Roman" FontSize="16" Margin="10,0,0,0">
                    StyledButton2
                </Button>
                <Button FontFamily="{StaticResource ButtonFontFamily}" FontSize="{StaticResource ButtonFontSize}" Margin="10,0,0,0">
                    StyledButton3
                </Button>
                <Button Content="StyledButton4" Margin="10,0,0,0">
                    <Button.Style>
                        <Style>
                            <Style.Setters>
                                <Setter Property="Control.FontFamily" Value="{StaticResource ButtonFontFamily}"/>
                                <Setter Property="Control.FontSize" Value="{StaticResource ButtonFontSize}"/>
                            </Style.Setters>
                            <Style.Triggers>
                                <Trigger Property="Control.IsMouseOver" Value="True">
                                    <Setter Property="Control.FontWeight" Value="Bold"/>
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>
            </StackPanel>
        </GroupBox>
        <GroupBox Header="创建Style实现批量修改外观属性" Margin="0,10,0,0">
            <StackPanel Orientation="Horizontal">
                <Button Content="StyledButton1"/>
                <Button Style="{x:Null}" Content="StyledButton2" Margin="10,0,0,0"/>
                <Button Style="{StaticResource StyleButton}" Content="StyledButton3" Margin="10,0,0,0"/>
            </StackPanel>
        </GroupBox>
        <GroupBox Header="创建ControlTemplate实现自定义控件外观" Margin="0,10,0,0">
            <StackPanel Orientation="Horizontal">
                <Button Content="NormalButton" VerticalAlignment="Center"/>
                <Button Content="NewButton1" Style="{StaticResource StyleButtonWithControlTemplate}" Margin="10,0,0,0"/>
                <Button Content="NewButton2" Template="{StaticResource ControlTemplateButton}" Margin="10,0,0,0"/>
            </StackPanel>
        </GroupBox>
    </StackPanel>
</Window>

注解:
1.在 StackPanel.Resources 节点下定义了 FontFamily 类型资源和 sys:Double 类型资源,然后在 Button 元素中通过标记扩展语法引用上述资源,引用语法为 FontFamily="{StaticResource ButtonFontFamily}" FontSize="{StaticResource ButtonFontSize}"。通过这种方式,后续需要修改属性值时,只需要在资源定义处修改,不必改动到多处。
2.如果需要设置控件动态样式,如鼠标移到上面时的样式、鼠标点击时的样式,则需要在 Style 元素下使用属性元素的语法来设置 Style.Triggers 属性的值,Trigger 为触发器。
3.在窗口根元素 Window 节点下定义资源,则该页面所有地方都能引用到该资源。
4.假设 x:Key 为 StyleButton 的样式没有设置 x:Key 值,则页面中所有按钮默认都使用了这个样式,除了显示给 x:Key 赋值 x:Null 的按钮。
5.ControlTemplate 的两种实现方式归根到底是设置控件的 Template 属性,只不过在 Style 元素中设置该值能实现批量修改控件外观的功能。

3.绑定

WPF 绑定可以理解为一种关系,该关系告诉 WPF 从一个源对象提取一些信息,并将这些信息来设置目标对象的属性。

  • WPF 绑定,必须要有绑定目标和绑定数据源。
  • WPF 绑定语法是在 XAML 中使用 {Binding…} 语句,Binding 可以通过 XAML 语句实现界面与数据的耦合,一般情况下,Binding 源是逻辑层的对象,Binding 目标是UI层的控件对象。
  • WPF 绑定使得原本需要多行代码实现的功能,现在只需通过简单的 XAML 代码就可以完成。
    在这里插入图片描述

简单的理解:WPF 绑定特性使得在修改对象的值后,展现对象的界面会自行刷新,不用在代码中手动更新界面展示。如果使用其他UI框架,则可能的执行操作流程是:修改对象的值----更新界面负责展示该值的控件的内容。通过下面的例子可以体会下 WPF 绑定特性的便捷性。

<Window x:Class="WPFLearn.Views.DataBindingDemo"
        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:WPFLearn.Views"
        mc:Ignorable="d"
        Title="数据绑定之绑定控件" Height="300" Width="400"
        x:Name="window">
    <StackPanel HorizontalAlignment="Stretch" Margin="10">
        <ListBox x:Name="ListColor">
            <ListBoxItem Content="Blue"/>
            <ListBoxItem Content="Red"/>
            <ListBoxItem Content="Green"/>
            <ListBoxItem Content="Gray"/>
            <ListBoxItem Content="Cyan"/>
        </ListBox>
        <TextBlock Text="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Background="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Margin="0,10,0,0"/>
        <TextBox Text="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=TwoWay}" Background="{Binding ElementName=ListColor,Path=SelectedItem.Content,Mode=OneWay}" Margin="0,10,0,0"/>
        <Button Margin="0,10,0,0">切换焦点</Button>
    </StackPanel>
</Window>
public partial class DataBindingDemo : Window
{
    public DataBindingDemo()
    {
        InitializeComponent();
    }
}

注解:
1.第一段代码段为窗口的 XAML 文件内容,第二代码段为窗口的 CS 文件内容。
2.该窗口实现的功能:切换列表选择项,下方的文本显示框和文本编辑框的内容和背景色会跟着改变;修改文本编辑框的内容,然后点击切换焦点的按钮,列表选中项的内容会变为文本编辑框中的内容,同时文本显示框的内容、背景颜色与文本编辑框的背景颜色会跟着改变。
3.我们发现在窗口的 CS 文件中,我们没有写任何额外的代码,比如更新控件内容、更新控件背景颜色,我们只是在 XAML 文件中使用了 {Binding…} 这样的绑定语法,便实现了数据同步这一功能,这便是 WPF 绑定的便捷性,也体现出了 WPF 的威力。

3.1数据源

WPF 绑定的数据源需要具有通知更改功能,有两种方法设计这种数据源:

  • 使用 DependencyObject 类型(依赖属性)。
  • 使用实现 INotifyPropertyChanged 接口的类型。

3.2绑定方法

1.ItemsControl

  • 在代码中将集合赋值给控件的 ItemsSource 属性,在 XAML 文件中就不用给控件的 ItemsSource 属性赋值。
  • 在 XAML 文件中,给控件的 ItemsSource 属性赋值 “{Binding}” ,然后在代码初始化窗口时,将集合作为 DataContext 。
  • 在 XAML 文件中,首先使用 ObjectDataProvider 对象标签,设置其 ObjectType、MethodName 属性,这两个属性能够定位提供数据源的方法。然后给控件的 ItemsSource 属性赋值 “{Binding Source={StaticResource DataList}}” 。其中 DataList 为 ObjectDataProvider 标签中的 x:Key 属性的值。

2.ContentControl
在 XAML 文件中,给控件的属性赋值 “{Binding Path=AttributeName}” ,然后在代码初始化窗口时,将窗口本身赋值给 DataContext 。

3.3绑定模式

控制绑定机制的关联性,比如单向、双向还是单次…
1.属性:Mode 。
2.枚举值:见下表。

枚举值意义
Default使用绑定目标的默认 Mode 值。每个依赖项属性的默认值都不同。一般情况下,用户可编辑控件属性(例如文本框和复选框的属性)默认为双向绑定,而多数其他属性默认为单向绑定。
TwoWay源属性或目标属性的更改可自动更新对方。
OneWay当绑定源(源)更改时,更新绑定目标(目标)属性。如果无需监视目标属性的更改,则使用 OneWay 绑定模式可避免 TwoWay 绑定模式的系统开销。
OneWayToSource当目标属性更改时更新源属性。
OneTime当应用程序启动或数据上下文更改时,更新绑定目标。如果要从源属性初始化具有某个值的目标属性,并且事先不知道数据上下文,则也可以使用此绑定类型。此绑定类型实质上是 OneWay 绑定的简化形式,在源值不更改的情况下可以提供更好的性能。

3.4绑定更新

控制绑定机制触发更新的时机,比如失焦、属性更改…
1.属性:UpdateSourceTrigger 。
2.枚举值:见下表。

枚举值意义
Default绑定目标属性的默认更新值,大多数依赖项属性的默认值都为 PropertyChanged ,而 Text 属性的默认值为 LostFocus 。
Explicit仅在调用 UpdateSource 方法时更新绑定源。
LostFocus当绑定目标元素失去焦点时,更新绑定源。
PropertyChanged当绑定属性更改时,立即更新绑定源。

3.5示例

<Window x:Class="WPFLearn.Views.DataBinding.BindingDataDemo"
        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:WPFLearn.Views.DataBinding"
        mc:Ignorable="d"
        x:Name="window"
        Title="数据绑定之绑定对象" Height="300" Width="400">
    <Grid >
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <StackPanel x:Name="PanelStudentInfo">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="学号:"/>
                    <TextBlock Text="{Binding Path=ID}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <TextBlock Text="姓名:"/>
                    <TextBlock Text="{Binding Path=Name}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <TextBlock Text="分数:"/>
                    <TextBlock Text="{Binding Path=Score}" Width="100"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                    <Button Content="改变姓名" Name="ButtonChangeName" Click="ButtonChangeName_Click"/>
                    <Button Content="改变分数" Name="ButtonCHangeScore" Click="ButtonCHangeScore_Click" Margin="20,0,0,0"/>
                </StackPanel>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,20,0,0">
                <TextBlock Text="学校:"/>
                <TextBlock x:Name="TextBlockSchool" Text="{Binding Path=School}"/>
                <Button x:Name="ButtonChangeSchool" Content="改变学校" Width="60" Click="ButtonChangeSchool_Click" Margin="10,0,0,0"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
namespace WPFLearn.Views.DataBinding
{
    /// <summary>
    /// BindingDataDemo.xaml 的交互逻辑
    /// </summary>
    public partial class BindingDataDemo : Window
    {
        //类型1:继承INotifyPropertyChanged的可发通知消息的类型
        private Student student;

        //类型2:依赖属性
        public string School
        {
            get { return (string)GetValue(SchoolProperty); }
            set { SetValue(SchoolProperty, value); }
        }

        public static DependencyProperty SchoolProperty =
            DependencyProperty.Register("School", typeof(string), typeof(BindingDataDemo), new PropertyMetadata("清华大学"));

        public BindingDataDemo()
        {
            InitializeComponent();
            student = new Student() { ID = 1, Name = "小红", Score = 98 };
            //绑定方式1:给元素对象设置DataContext属性,然后在XAML文件中直接引用
            this.PanelStudentInfo.DataContext = student;
            this.TextBlockSchool.DataContext = this;

            绑定方式2:手写Binding TODO:存在问题,没办法更新数据,原因暂时不清楚
            //Binding bind = new Binding();
            //bind.Source = this;
            //bind.Path = new PropertyPath("School");
            //bind.Mode = BindingMode.TwoWay;
            //bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            //this.TextBlockSchool.SetBinding(TextBlock.TextProperty, bind);
            BindingOperations.SetBinding(this.TextBlockSchool, TextBlock.TextProperty, bind);
        }

        private void ButtonChangeName_Click(object sender, RoutedEventArgs e)
        {
            student.Name = "小明";
        }

        private void ButtonCHangeScore_Click(object sender, RoutedEventArgs e)
        {
            student.Score = 100;
        }

        private void ButtonChangeSchool_Click(object sender, RoutedEventArgs e)
        {
            School = "北京大学";
        }

        public class Student : INotifyPropertyChanged
        {
            private int id;

            public int ID
            {
                get { return id; }
                set
                {
                    if (id != value)
                    {
                        id = value;
                        OnPropertyChanged(new PropertyChangedEventArgs("ID"));
                    }
                }
            }

            private string name;

            public string Name
            {
                get { return name; }
                set
                {
                    if (name != value)
                    {
                        name = value;
                        OnPropertyChanged(new PropertyChangedEventArgs("Name"));
                    }
                }
            }

            private double score;

            public double Score
            {
                get { return score; }
                set
                {
                    score = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Score"));
                }
            }



            public event PropertyChangedEventHandler PropertyChanged;
            public void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, e);
            }
        }
    }
}

注解:
1.Student 类型实现了 INotifyPropertyChanged,因此 Student 类型的 student 字段可以作为绑定数据源;而窗口 BindingDataDemo 内部添加了 DependencyProperty 类型的依赖属性 School,因此 School 也可以作为绑定数据源。
2.在窗口构造函数中,给控件的数据上下文 DataContext 属性赋值,这样在 XAML 中,控件就可以直接通过 {Binding Path=XXX} 找到XXX所属的对象,然后建立绑定关系。
3.最终,我们在修改函数中,单纯修改内部数据,不用手动更新控件展示值,控件便会自动刷新显示。

4.模板

在 WPF 中包含三种模板:控件模板、数据模板和面板模板,它们都继承于 FrameworkTemplate 基类。

4.1控件模板

控件模板(System.Windows.Controls.ControlTemplate),即控件外观,可以通过修改控件模板来自定义控件的外观表现,例如,可以通过修改按钮的控件模板使按钮表现为圆形。控件模板控制UI整体外观。
1.资源定义:ControlTemplate。
2.属性使用:Template。
3.例子:

<Window x:Class="WPFLearn.Views.Template.ControlTemplateDemo"
        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:WPFLearn.Views.Template"
        mc:Ignorable="d"
        Title="模板之控件模板" Height="450" Width="800">
    <Window.Resources>
        <ControlTemplate x:Key="RoundButtonTemplate" TargetType="Button">
            <Grid>
                <Ellipse Name="EllipseSection" Fill="Orange" Width="100" Height="100"/>
                <ContentPresenter Content="{TemplateBinding Button.Content}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="EllipseSection" Property="Fill" Value="Yellow"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>
    <Grid >
        <Button Content="Round Button" HorizontalAlignment="Center"  VerticalAlignment="Center" Template="{StaticResource RoundButtonTemplate}"/>
    </Grid>
</Window>

4.2数据模板

数据模板(System.Windows.DataTemplate),即数据外观,用于从一个对象中提取数据,并在控件中显示数据。只有内容控件与条目控件支持数据模板。数据模板控制UI内容形式
1.资源定义:DataTemplate 。
2.属性使用:ContentTemplate(内容控件)、ItemTemplate(条目控件)。
3.例子:

<Window x:Class="WPFLearn.Views.Template.DataTemplateDemo"
        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:WPFLearn.Services"
        mc:Ignorable="d"
        Title="模板之数据模板" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="PersonDataTemplate">
            <Border Name="BorderBlue" Margin="3" BorderThickness="3" BorderBrush="Blue" CornerRadius="5">
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="TextBlockName" FontWeight="Bold" Text="{Binding Path=Name}"/>
                    <TextBlock Grid.Row="1" Text="{Binding Path=Age}"/>
                </Grid>
            </Border>
            <DataTemplate.Triggers>
                <Trigger SourceName="BorderBlue" Property="IsMouseOver" Value="True">
                    <Setter TargetName="BorderBlue" Property="Background" Value="LightGray"/>
                    <Setter TargetName="TextBlockName" Property="FontSize" Value="20"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>
    <Grid Margin="0,0,0,10">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="60"/>
        </Grid.RowDefinitions>
        <ListBox x:Name="ListBoxPerson" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch" ItemTemplate="{StaticResource PersonDataTemplate}"/>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0">
            <Button x:Name="ButtonAddAgeOfFirstItem" Content="递增第一项年龄值" Height="30" Click="ButtonAddAgeOfFirstItem_Click"/>
            <Button x:Name="ButtonDeleteLastItem" Content="删除第二项" Height="30" Margin="10,0,0,0" Click="ButtonDeleteLastItem_Click"/>
        </StackPanel>
    </Grid>
</Window>

4.3面板模板

面板模板(System.Windows.Controls.ItemsPanelTemplate),即面板外观,而面板又是用于进行布局的,所以面板的外衣也就是布局的外衣,通过修改面板模板可以自定义控件的布局。例如,ListBox默认是自从向下地显示每一项的,此时可以通过修改面板模板使其自左向右地显示每一项。面板模板控制UI内容布局
1.资源定义:ItemsPanelTemplate 。
2.属性使用:ItemsPanel 。
3.例子:

<Window x:Class="WPFLearn.Views.Template.PanelTemplateDemo"
        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:WPFLearn.Views.Template"
        mc:Ignorable="d"
        Title="模板之面板模板" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="PersonDataTemplate">
            <Border x:Name="BorderBlue" Margin="3" BorderThickness="3" BorderBrush="Blue" CornerRadius="5">
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="TextBlockName" FontWeight="Bold" Text="{Binding Name}"/>
                    <TextBlock Grid.Row="1" Text="{Binding Age}"/>
                </Grid>
            </Border>
            <DataTemplate.Triggers>
                <Trigger SourceName="BorderBlue" Property="IsMouseOver" Value="True">
                    <Setter TargetName="BorderBlue" Property="Background" Value="LightGray"/>
                    <Setter TargetName="TextBlockName" Property="FontSize" Value="20"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="ListItemsPanelTemplate">
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </ItemsPanelTemplate>
    </Window.Resources>
    <Grid>
        <ListBox x:Name="ListBoxPerson" ItemsPanel="{StaticResource ListItemsPanelTemplate}" ItemTemplate="{StaticResource PersonDataTemplate}"/>
    </Grid>
</Window>

5.资源

WPF 资源系统是一种保管一系列对象(如常用的画刷、样式或模板)的简单办法,从而让使用者更容易地复用这些对象。

5.1资源定义

每一个框架级元素( FrameworkElement 或者 FrameworkContentElement )都有一个资源属性。每一个在资源字典中的资源都有一个唯一不重复的键值( key ),在标签中使用 x:Key 属性来标识它。一般地,键值是一个字符串,但你也可以用合适的扩展标签来设置为其他对象类型。非字符键值资源使用于特定的WPF 区域,尤其是风格、组件资源以及样式数据等。

5.2资源使用

为了使用 XAML 标记中的资源,需要一种引用资源的方法,可以通过两个标记来引用资源:StaticResource 和 DynamicResource 。

  • StaticResource :以静态方式引用资源。在第一次创建窗口时,一次性地设置完毕,程序运行过程中资源发生变化,程序不会更新引用资源的对象。无法实时更新程序,效率高。
  • DynamicResource :以动态方式引用资源。如果资源发生了改变,程序会重新应用资源。可以实时更新程序,效率低。

5.3资源范围

WPF 提供一个封装和存取资源的机制,我们可将资源建立在应用程序的不同范围上。WPF中,资源定义的位置决定了该资源的可用范围,程序会按照范围由小到大的顺序检索资源。资源按照范围分类见下表。

级别说明
物件级资源定义在普通元素下,该资源只能套用在这个元素及其其子元素。
文件级资源定义在Window或Page元素下,该资源可以套用到这个文件中的所有元素。
应用程序级资源定义在App.xaml中,该资源可以套用到应用程序内的任何地方。
字典级将资源封装成资源字典定义在资源XAML文件中,该资源可以在套用到其他应用程序中。
系统级资源检索的最终层级,我们无法在这层级上自定义资源,系统级资源有系统颜色设置/字体设置/屏幕标准尺寸等。

5.4综合示例

1.StaticResource 和 DynamicResource 的区别

<Window x:Class="WPFLearn.Views.ResourceStyle.ResourceStyleDemo"
        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:WPFLearn.Views.ResourceStyle"
        mc:Ignorable="d"
        Title="ResourceStyleDemo" Height="300" Width="300">
    <Window.Resources>
        <SolidColorBrush x:Key="RedBrush" Color="Red"></SolidColorBrush>
    </Window.Resources>
    <StackPanel Margin="5">
		<StackPanel>
			<Button Background="{StaticResource RedBrush}" Margin="5" FontSize="14" Content="Use Static Resource"/>
			<Button Background="{DynamicResource RedBrush}" Margin="5" FontSize="14" Content="Use Dynamic Resource"/>
			<Button Margin="5" FontSize="14" Content="Change the RedBrush to Yellow" Click="Button_Click"/>
		</StackPanel>
    </StackPanel>
</Window>
public partial class ResourceStyleDemo : Window
{
	public ResourceStyleDemo()
	{
		InitializeComponent();
	}

	private void Button_Click(object sender, RoutedEventArgs e)
	{
		this.Resources["RedBrush"] = new SolidColorBrush(Colors.Yellow);
	}
}

注解: 点击按钮后,Use Static Resource 的按钮背景颜色没改变,Use Dynamic Resource 的按钮背景颜色改变。

2.各种级别的资源
物件级和文件级两个级别的资源之前的示例都有见过,下面是字典级别的,在 App.xaml 引用字典级别资源,相当于定义了应用级别资源,另外,应用级别资源定义同其它级别资源定义一样,所以应用级别资源也不做演示。

<!--CheckBoxStyle.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="CheckBoxStyle" TargetType="{x:Type CheckBox}">
        <Setter Property="IsChecked" Value="False"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="CheckBox">
                    <DockPanel Background="{TemplateBinding Background}" ToolTip="{TemplateBinding Content}" LastChildFill="False" Width="{TemplateBinding Width}">
                        <Image Margin="0" DockPanel.Dock="Left" x:Name="CheckImage" Stretch="Fill" Source="/Uires/复选框未选.png" Width="15" Height="15"/>
                        <TextBlock DockPanel.Dock="Left" Foreground="{TemplateBinding Foreground}" Margin="2,0,0,0" VerticalAlignment="Center" Text="{Binding Content,RelativeSource={RelativeSource TemplatedParent}}"/>
                    </DockPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter TargetName="CheckImage" Property="Source" Value="/Uires/复选框已选.png"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
<!--Style.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/Styles/ButtonStyle.xaml"/>
        <ResourceDictionary Source="pack://application:,,,/Styles/CheckBoxStyle.xaml"/>
        <ResourceDictionary Source="pack://application:,,,/Styles/OtherStyle.xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<!--App.xaml-->
<Application x:Class="WPFLearn.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WPFLearn"
             StartupUri="Views/Control/ControlClassificationDemo.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/Styles/Style.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

注解:
1.CheckBoxStyle.xaml 定义了 CheckBox 样式,Style.xaml 包含所有控件样式文件,App.xaml 引入 Style.xaml。
2.CheckBoxStyle.xaml 和 Style.xaml 都定义了字典级的资源,在 App.xaml 中定义的资源便是应用程序级的资源。

6.样式

WPF 中通过样式可以方便地批量设置控件外观。

6.1样式定义

在 XAML 中通过使用 Style 元素定义样式,既可以在控件元素中定义样式(样式用于控件自身),也可以在 Resource 元素中使定义样式(样式可以被范围内的所有控件引用)。
在 Style 元素中,通过 Style.Setters 元素定义样式静态内容,通过 Style.Triggers (触发器)元素定义样式动态内容。
1.WPF中的触发器类型
(1)属性触发器(Property Trigger):当Dependency Property的值发生改变时触发。Trigger 和 MultiTriiger 。
(2)数据触发器(Data Trigger):当普通.NET属性的值发生改变时触发。DataTrigger 和 MultiDataTrigger。
(3)事件触发器(Event Trigger):当路由事件发生时触发。EventTrigger 。

注解: 触发器的 Multi 版本适用多条件的场景,比如设置复选框 选中状态+鼠标移动到上面 的样式。

6.2样式使用

1.在控件元素中定义的样式
不需要显示引用,效果相当直接在控件元素中给各种样式属性赋值。
2.在Resource元素中定义样式

  • x:Key 属性为空的样式:样式通过 TargetType 属性指定控件类型,该类型的所有控件实例都会隐式引用该样式。如果想要取消引用,需要显示地给样式属性赋值 Style = “{x:Null}”
  • x:Key 属性不为空的样式:需要显示地给控件元素的样式属性赋值 Style = “{StaticResouce StyleKey}”

7.路由事件

WPF 除了创建了一个新的依赖属性系统之外,还用更高级的路由事件功能替换了普通的 .NET 事件。
路由事件是具有更强传播能力的事件——它可以在元素树上向上冒泡和向下隧道传播,并且沿着传播路径被事件处理程序处理。

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>
private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}

8.命令模式

1.概述
在现在的用户界面中,常常需要从多个不同位置访问通过函数,即由不同的动作触发同样响应。在这种情况下,如果使用路由事件,则需要定义多个事件,然后在这些事件中调用同个函数,这样处理会很繁琐。WPF 使用命令机制来解决上述问题:WPF 允许在一个地方定义命令,并且在所有的用户接口控件之中根据需要地调用这些命令,例如 Menu,ToolBar 按钮等,不同动作最终会得到相同的响应。

2.WPF 中的命令模型

  • 命令:命令表示一个程序任务,并且可跟踪该任务是否能被执行。然而,命令实际上不包含执行应用程序的代码,真正处理程序在命令目标中。
  • 命令源:命令源触发命令,即命令的发送者。例如 Button、MenuItem 等控件都是命令源,单击它们都会执行绑定的命令。
  • 命令目标:命令目标是在其中执行命令的元素。如 Copy 命令可以在 TextBox 控件中复制文本。
  • 命令绑定:命令是不包含执行程序的代码的,真正处理程序存在于命令目标中。那命令是怎样映射到处理程序中的呢?这个过程就是通过命令绑定来完成的,命令绑定完成的就是红娘牵线的作用。

3.WPF 命令的核心
ICommand 接口,该接口定义了命令的工作原理。

9.MVVM

1.概述
MVVM 是 WPF 衍生出的一种优秀编程框架(模式),这种模式充分利用了 WPF 的数据绑定机制,最大限度地降低了 XAML 文件和 CS 文件的耦合度,实现了将视图UI和业务逻辑分离的效果。在需要更换界面时,逻辑代码修改很少,甚至不用修改。
2.定义

  • MVVM 编程模式通常被用于 WPF 或 Silverlight 开发,一些现代的 WEB 前端框架也使用了这种模式。
  • MVVM 是 Model-View-ViewModel 的缩写形式。
    模型(Model):数据对象,负责存储数据。
    视图(View):界面对象,负责与用户交互、接收用户输入、把数据展现给用户。
    视图模型(View Model):连接对象,负责连接 Model 层和 View 层。View Model 不仅仅是 Model 的包装,它还包含了程序逻辑以及 Model 扩展。
    在这里插入图片描述

3.优点

  • 低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
  • 可重用性:可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用 Expression Blend 可以很容易设计界面并生成XAML代码。

4.在 WPF 中使用 MVVM 时的特点

  • 视图的 CS 文件包含极少的代码,其核心逻辑都被放在 View Model 类中,从而使得程序逻辑与视图耦合度降低。
  • ViewModel 类作为 View 的 DataContext 。
  • 在 MVVM 下,所有事件和动作都被当成命令,如按钮的点击操作,此时不是触发点击事件,而是绑定到一个点击命令,再由命令去执行对应的逻辑代码。

5.示例
WPF快速入门系列(8)——MVVM快速入门,这位作者写的这轮系列文章都很不错,推荐大家入门 WPF 时去看,地址是WFP快速入门系列 | Learning hard | 博客园

10.参考资料

1.先说下自己推荐的学习路线:
(1)先用1.5倍速看一遍《深入浅出WPF》系列高清视频教程 | 刘铁猛 | B站 的视频。
(2)再认真把 WFP快速入门系列 | Learning hard | 博客园 的系列文章都过一遍。
(3)最后找个现有的客户端程序进行模仿练习,用到哪些控件再上网查下具体用法。

2.参考资料大致如下:
《深入浅出WPF》系列高清视频教程 | 刘铁猛 | B站
WFP快速入门系列 | Learning hard | 博客园
WPF入门教程系列 | DotNet菜园 | 博客园
WPF基础之资源 | SmilelyCoding | 博客园
WPF教程 | vue5.com在线教程

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值