深入浅出WPF学习笔记

深入浅出WPF学习笔记

  • 第一部分 深入浅出话XAML
    • 第3章 系统学习XAML语法
      • 3.2 XAML中为对象属性赋值的语法
        • 3.2.4 标记扩展(Markup Extensions)
    • 第5章 控件与布局
      • 5.1 控件与布局
      • 5.2 WPF的内容模型
      • 5.3 各类内容模型详解
        • 5.3.1 ContentControl 族
        • 5.3.2 HeaderedContentControl族
        • 5.3.3 ItemsControl族
        • 5.3.4 HeaderedItemsControl族
        • 5.3.5 Decorator族
        • 5.3.6 TextBlock和TextBox
        • 5.3.7 Shape族元素
        • 5.3.8 Panel族元素
      • 5.4 UI布局(Layout)
        • 5.4.1 布局元素
        • 5.4.2 Gird
          • 1. 定义Gird的行与列
          • 2. 使用Gird进行布局
        • 5.4.3 StackPanel
        • 5.4.4 Canvas
        • 5.4.5 DockPanel
        • 5.4.6 WrapPanel
  • 第二部分 游历WPF内部世界
    • 第6章 深入浅出话Binding
      • 6.2 Binding基础
      • 6.3 Binding的源与路径
        • 6.3.1 把控件作为Binding源与Binding标记扩展
        • 6.3.2 控制Binding的方向及数据更新
        • 6.3.3 Binding的路径(Path)
        • 6.3.4 “没有Path”的Binding
        • 6.3.5 为Binding指定源(Source)的几种方法
        • 6.3.6 没有Source的Binding---使用DataContext作为Binding的源
        • 6.3.7 使用集合对象作为列表控件的ItemsSource
  • 附:常用的方法属性

第一部分 深入浅出话XAML

第3章 系统学习XAML语法

3.2 XAML中为对象属性赋值的语法

3.2.4 标记扩展(Markup Extensions)
Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"

这句就是标记扩展

  • 当编译器看到这句代码时就会把花括号里的内容解析成相应的对象。
  • 对象的数据类型名是紧邻左花括号的字符串。
  • 对象的属性由一串以逗号连接的子字符串负责初始化(注意,属性值不再加引号)。
    尽管标记扩展的语法简洁方便,但并不是所有对象都能用标记扩展的语法来书写,只有MarkupExtension类的派生类(直接或间接均可)才能使用标记扩展语法来创建对象。MarkupExtension的直接派生类并不多,它们是:
    深入浅出WPF学习笔记_第1张图片
    深入浅出WPF学习笔记_第2张图片

第5章 控件与布局

5.1 控件与布局

日常工作做打交道最多的控件无外乎6类

名称 解释
布局控件 可以容纳多个控件或嵌套其他布局控件,用于在UI上组织和排列控件。Grid、StackPanel、DockPanel等控件都属此类,它们拥有共同的父类 Panel。
内容控件 只能容纳一个其他控件或布局控件作为它的内容。Window、Button等控件属于此类,因为只能容纳一个控件作为其内容,所以经常需要借助布局控件来规划其内容。它们的共同父类是ContentControl。
带标题内容控件 相当于一个内容控件,但可以加一个标题(Header),标题部分亦可容纳一个控件或布局。GroupBox、TabItem等是这类控件的典型代表。它们的共同父类是HeaderedContentControl。
条目控件 可以显示一列数据,一般情况下这列数据的类型相同。此类控件包括ListBox、ComboBox等。它们的共同基类是ItemsControl。此类控件在显示集合类型数据方面功能非常强大。它们的共同父类是ItemsControl。
带标题条目控件 相当于一个条目控件加上一个标题显示区。TreeViewItem、Menultem都属于此类控件。这类控件往往用于显示层级关系数据,结点显示在其Header区域,子级结点则显示在其条目控件区域。此类控件的共同基类是HeaderedItemsControl.
特殊内容控件 比如TextBox容纳的是字符串、TextBlock可以容纳可自由控制格式的文本、Image容纳图片类型数据……这类控件相对比较独立。

6类控件的派生关系如下图

深入浅出WPF学习笔记_第3张图片

5.2 WPF的内容模型

WPF的UI元素类型如下图
深入浅出WPF学习笔记_第4张图片

5.3 各类内容模型详解

我们把符合某类内容模型的UI元素称为一个族,每个族用它们共同基类来命名。

5.3.1 ContentControl 族

特点如下:

  • 均派生自ContentControl类
  • 他们都是控件(Control)
  • 内容属性的名称为Content
  • 只能由单一元素充当其内容
    深入浅出WPF学习笔记_第5张图片
5.3.2 HeaderedContentControl族

特点如下:

  • 它们都派生自HeaderedContentControl类,
    HeaderedContentControl是ContentControl类的派生类。
  • 它们都是控件,用于显示带标题的数据。
  • 除了用于显示主体内容的区域外,控件还具有一个显示标题(Header)的区域。
  • 内容属性为Content和 Header 。
  • 无论是 Content还是Header都只能容纳一个元素作为其内容。
    在这里插入图片描述
5.3.3 ItemsControl族

特点如下:

  • 均派生自ItemsControl类。
  • 它们都是控件,用于显示列表化的数据。
  • 内容属性为 Items或ItemsSource。
  • 每种ItemsControl都对应有自己的条目容器( Item Container)。
    深入浅出WPF学习笔记_第6张图片
    深入浅出WPF学习笔记_第7张图片
5.3.4 HeaderedItemsControl族

特点如下:

  • 均派生自HeaderedItemsControl类。
  • 它们都是控件,用于显示列表化的数据,同时可以显示一个标题。
  • 内容属性为Items、ItemsSource和 Header。
    本组控件只有三个:MenuItem、TreeViewItem、ToolBar
5.3.5 Decorator族

本族中的元素是在UI上起装饰效果的。如可以使用Border元素为一些组织在一起的内容加个边框。如果需要组织在一起的内容能够自由缩放,则可使用ViewBox元素。
特点如下:

  • 均派生自Decorator类。
  • 起UI装饰作用。
  • 内容属性为Child。
  • 只能由单一元素充当内容。
    深入浅出WPF学习笔记_第8张图片
5.3.6 TextBlock和TextBox

这两个控件最主要的功能是显示文本。

  1. TextBlock 只能显示文本,不能编辑,所以又称静态文本。
  2. TextBox 则允许用户编辑其中的内容。TextBlock 虽然不能编辑内容,但可以使用丰富的印刷级的格式控制标记显示专业的排版效果。
  • TextBox不需要太多的格式显示,所以它的内容是简单的字符串,内容属性为Text。
  • TextBlock 由于需要操纵格式,所以内容属性是Inlines(印刷中的“行”),同时,TextBlock也保留一个名为Text的属性,当简单地显示一个字符串时,可以使用这个属性。
5.3.7 Shape族元素

友好的用户界面离不开各种图形的搭配,Shape族元素(它们只是简单的视觉元素,不是控件)就是专门用来在UI上绘制图形的一类元素。这类元素没有自己的内容,我们可以使用Fill属性为它们设置填充效果,还可以使用Stroke属性为它们设置边线的效果。
特点如下:

  • 均派生自Shape类。
  • 用于2D图形绘制。
  • 无内容属性。
  • 使用Fill属性设置填充,使用Stroke属性设置边线。
5.3.8 Panel族元素

所有用于UI布局的元素都属于这一族
特点如下:

  • 均派生自Panel抽象类。
  • 主要功能是控制UI布局。
  • 内容属性为Children。
  • 内容可以是多个元素,Panel元素将控制它们的布局。
    深入浅出WPF学习笔记_第9张图片

5.4 UI布局(Layout)

5.4.1 布局元素

WPF中的布局元素有以下几个:
深入浅出WPF学习笔记_第10张图片

5.4.2 Gird

Grid元素会以网格的形式对内容元素们(即它的 Children)进行布局。
特点如下:

  • 可以定义任意数量的行和列,非常灵活。
  • 行的高度和列的宽度可以使用绝对数值、相对比例或自动调整的方式进行精确设定,并可设置最大和最小值。
  • 内部元素可以设置自己的所在的行和列,还可以设置自己纵向跨几行、横向跨几列。
  • 可以设置Children元素的对齐方向。

基于这些特点,Grid适用的场合有:

  • UI布局的大框架设计。
  • 大量UI元素需要成行或者成列对齐的情况。
  • UI整体尺寸改变时,元素需要保持固有的高度和宽度比例。
  • UI后期可能有较大变更或扩展。
1. 定义Gird的行与列

Grid类具有ColumnDefinitions 和RowDefinitions两个属性,它们分别是ColumnDefinition和RowDefinition 的集合,表示Grid定义了多少列、多少行。
只定义行和列的个数还远远不够,我们还需要设置行的高度和列的宽度才能形成有意义的布局。这就引出两个问题:
宽度和高度的单位是什么。
宽度和高度可以取什么样的值。
深入浅出WPF学习笔记_第11张图片
深入浅出WPF学习笔记_第12张图片

2. 使用Gird进行布局
<Window x:Class="demo.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:demo"
        mc:Ignorable="d"
        Title="留言板" Height="240" Width="400"
        MinHeight="200" MinWidth="340" MaxHeight="400" MaxWidth="600">
    <Grid Margin="10">       
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="120"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="4"/>
            <ColumnDefinition Width="80"/>
        Grid.ColumnDefinitions>
        <Grid.RowDefinitions >
            <RowDefinition Height="25"/>
            <RowDefinition Height="4"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="4"/>
            <RowDefinition Height="25"/>
        Grid.RowDefinitions>
        <TextBlock Text="请选择您的部门并留言:" Grid.Column="0" Grid.Row="0" VerticalAlignment="Center"/>
        <ComboBox Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="4"/>
        <TextBox Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="5" BorderBrush="Black"/>
        <Button Content="提交" Grid.Column="2" Grid.Row="4" />
        <Button Content="清除" Grid.Column="4" Grid.Row="4" />
    Grid>
Window>

效果图:
深入浅出WPF学习笔记_第13张图片
深入浅出WPF学习笔记_第14张图片

5.4.3 StackPanel

StackPanel可以把内部元素在纵向或横向上紧凑排列、形成栈式布局,通俗地讲就是把内部元素像垒积木一样“撂起来”。垒积木大家都玩过,当把排在前面的积木块抽掉之后排在它后面的元素会整体向前移动、补占原有元素的空间。基于这个特点,StackPanel适合的场合有:

  • 同类元素需要紧凑排列(如制作菜单或者列表)。
  • 移除其中的元素后能够自动补缺的布局或者动画。
    StackPanel使用3个属性来控制内部元素的布局,它们是Orientation、HorizontalAlignment和VerticalAlignment,具体如表5-9所示。
    深入浅出WPF学习笔记_第15张图片
<Window x:Class="demo.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:demo"
        mc:Ignorable="d"
        Title="选择题" Height="190" Width="300">
    <Grid Margin="10">
     <GroupBox Header="请选择没有错别字的成语" BorderBrush="Black" Margin="5">
            <StackPanel Margin="5"  >
                <CheckBox Content="A. 迫不及待"/>
                <CheckBox Content="B. 首屈一指"/>
                <CheckBox Content="C. 陈词滥调"/>
                <CheckBox Content="D. 唉声叹气"/>
                <CheckBox Content="E. 不可理喻"/>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <Button Content="清空" Width="60" Margin="5"/>
                    <Button Content="确定" Width="60" Margin="5"/>
                StackPanel>
            StackPanel>
        GroupBox>
    Grid>
Window>

效果图:深入浅出WPF学习笔记_第16张图片

5.4.4 Canvas

Canvas 译成中文就是“画布”,显然,在Canvas里布局就像在画布上画控件一样。使用Canvas布局与在 Windows Form窗体上布局基本上是一样的,只是在Windows Form开发时我们通过设置控件的Left和Top等属性来确定控件在窗体上的位置,而WPF的控件没有Left和Top等属性,就像把控件放在Grid里时会被附加上 Grid.Column和Grid.Row属性一样,当控件被放置在 Canvas里时就会被附加上 Canvas.X和 Canvas.Y属性。
Canvas适用的场合包括:

  • 一经设计基本上不会再有改动的小型布局(如图标)。
  • 艺术性比较强的布局。
  • 需要大量使用横纵坐标进行绝对点定位的布局。
  • 依赖于横纵坐标的动画
5.4.5 DockPanel

DockPanel内的元素会被附加上 DockPanel.Dock这个属性,这个属性的数据类型为Dock 枚举。Dock枚举可取 Left、Top、Right和 Bottom四个值。根据Dock 属性值,DockPanel内的元素会向指定方向累积、切分DockPanel内部的剩余可用空间,就像船舶靠岸一样。
DockPanel还有一个重要属性——bool类型的LastChildFill,它的默认值是True。当LastChildFill属性的值为True时,DockPanel内最后一个元素的 DockPanel.Dock 属性值会被忽略,这个元素会把DockPanel 内部所有剩余空间充满。这也正好解释了为什么Dock枚举类型没有Fill 这个值。

5.4.6 WrapPanel

WrapPanel内部采用的是流式布局。WrapPanel使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment 和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPanel会排列尽可能多的控件,排不下的控件将会新起一行或一列继续排列。

第二部分 游历WPF内部世界

第6章 深入浅出话Binding

6.2 Binding基础

需要设置Binding的源(Source)和目标(Target)
一般情况下,Binding源是逻辑层的对象,Binding目标是UI层的控件对象
数据源是一个对象,而一个对象身上会有多个数据(字段),数据(字段)通过属性暴露给外界,UI上的元素关联的其中某个属性值的变化,这个属性称为Binding的路径(Path)。
Binding是一个自动机制,属性变化后,要有能力通知Binding它已经改变,方法是在属性的set语句中激发一个PropertyChanged事件,事件不需要我们声明,我们只需要让作为数据源的类实现System.ComponentModel名称空间的INotifyPropertyChanged接口。当为Binding设置了数据源后,Binding就会自动侦听来自这个接口的PropertyChanged事件。
原本的Student类:

public class Student:
{
private string _name;
public string Name
        {
            get
            {
                return _name;
            }
			 set
            {
                _name = value;
             }
         } 
}

实现接口的Student类:

public class Student:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _name;       
        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                _name = value;
                //激发事件
                if(this.PropertyChanged!=null)
                {
                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
                }
            }
        }      
    }

经过这样一升级,当Name属性的值发生变化时 PropertyChanged事件就会被激发,Binding接收到这个事件后发现事件的消息告诉它是名为Name的属性发生了值的改变,于是就会通知Binding目标端的UI元素显示新的值。

public partial class MainWindow : Window
    {
        Student stu;
        public MainWindow()
        {
            InitializeComponent();
            //准备数据源
            stu = new Student();

            //准备 Binding
            Binding binding = new Binding();
            binding.Source = stu;
            binding.Path = new PropertyPath("Name");

            //使用Binding连接数据源与Binding目标
            BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
        }     
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            stu.Name += "Name";
        }       
    }

让我们逐句解读一下这段代码:这段代码是Window1类的后台部分,它的UI部分是上面给出的XAML代码。"Student stu;”是为Window1类声明了一个Student类型的成员变量,这样做的目的是为了在 Window1的构造器和 Button.Click事件处理器中都能访问由它引用的 Student 实例(数据源)。
在Windowl的构造器中“InitializeComponent();”是自动生成的代码,用途是初始化UI元素。“stu = new Student();”这句是创建了一个Student类型的实例并用stu成员变量引用它,这个实例就是我们的数据源。
在准备Binding的部分,先是用“Binding binding = new BindingO);”声明 Binding类型变量并创建实例,然后使用“binding.Source = stu;”为Binding 实例指定数据源,最后使用“binding.Path =new PropertyPath(“Name”);”语句为 Binding指定访问路径。
把数据源和目标连接在一起的任务是使用“BindingOperations.SetBinding(…)”方法完成的。这个方法的3个参数是我们记忆的重点:

  • 第一个参数用于指定 Binding的目标,本例中是this.textBoxName。
  • 与数据源的Path原理类似,第二个参数用于为 Binding 指明把数据送达目标的哪个属性。只是你会发现在这里用的不是对象的属性而是类的一个静态只读( static readonly)的DependencyProperty类型成员变量!这就是我们后面要详细讲述的与Binding 息息相关的依赖属性。其实很好理解,这类属性的值可以通过 Binding 依赖在其他对象的属性值上,被其他对象的属性值所驱动。
  • 第三个参数很明了,就是指定使用哪个Binding 实例将数据源与目标关联起来。
    深入浅出WPF学习笔记_第17张图片

6.3 Binding的源与路径

6.3.1 把控件作为Binding源与Binding标记扩展

下面的代码是把一个TextBox 的 Text 属性关联在了Slider 的 Value属性上。

<Window x:Class="demo_02.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:demo_02"
        mc:Ignorable="d"
        Title="MainWindow" Height="110" Width="300">
    <StackPanel>
        <TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/>
        <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5"/>
    StackPanel>
Window>

深入浅出WPF学习笔记_第18张图片

6.3.2 控制Binding的方向及数据更新

可以根据数据需要流通的方向设置单向、双向等等。
控制 Binding 数据流向的属性是Mode,它的类型是 BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OnTime、OneWayToSource和 Default。这里的Default值是指Binding的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text属性),Default就采用双向模式;若是只读的(如TextBlock.Text)则采用单向模式。

6.3.3 Binding的路径(Path)

尽管在XAML代码中或者Binding类的构造器参数列表中我们以一个字符串来表示 Path,但Path 的实际类型是PropertyPath。下面让我们看看如何创建Path 来应对各种情况。

  1. 最简单的情况就是直接把 Binding 关联在 Binding源的属性上。语法如下:

等效的C#代码

Binding binding =new Binding({Path=new PropertyPath("Value"), Source = this.sliderl};
this.textBox1.SetBinding(TextBox.TextProperty,binding);

或者使用Binding的构造器简写为:

Binding binding - new Binding("Value""){ Source = this.slider1};
this.textBox1.SetBinding(TextBox.TextProperty, binding);
  1. Binding还支持多级路径(通俗地讲就是一路“点”下去)。比如,如果我们想让一个 TextBox显示另外一个TextBox的文本长度,我们可以写:
<StackPanel>
<TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textBox2" Text="{Binding Path=Text.Length, ElementName=textBox1, Mode=OneWay)"
BorderBrush="Black" Margin="5"/>
StackPanel>

等效的C#代码

this.textBox2.SetBinding(TextBox.TextProperty, new Binding("Text.[3]"){ Source = this.textBoxl,ModeBindingMode.OneWay });

也可以省略掉Text.[3]中的”."
3. 当使用一个集合或者DataView作为Binding源时,如果我们想把它的默认元素当作Path使用,则需要使用这样的语法:
深入浅出WPF学习笔记_第19张图片
4. 如果集合元素的属性仍然还是一个集合,我们想把子级集合中的元素当作 Path,则可以使用多级斜线的语法(即一路“斜线”下去),例如:
深入浅出WPF学习笔记_第20张图片

6.3.4 “没有Path”的Binding

Binding源本身就是数据且不需要Path来说明。典型的string、int,无法指出通过哪个属性来访问这个数据,只需要将Path的值设置为“.”或干脆不写(在XAML中可以),在C#中不能省略

6.3.5 为Binding指定源(Source)的几种方法

Binding 的源是数据的来源,所以,只要一个对象包含数据并能通过属性把数据暴露出来,它就能当作 Binding 的源来使用。包含数据的对象比比皆是,但必须为Binding 的Source指定合适的对象Binding才能正确工作,常见的办法有:
深入浅出WPF学习笔记_第21张图片
深入浅出WPF学习笔记_第22张图片

6.3.6 没有Source的Binding—使用DataContext作为Binding的源

前面的例子都是把单个CLR类型对象指定为Binding 的Source,方法有两种——把对象赋值给Binding.Source属性或把对象的Name赋值给Binding.ElementName。
深入浅出WPF学习笔记_第23张图片

6.3.7 使用集合对象作为列表控件的ItemsSource

附:常用的方法属性

名称 含义
VerticalAlignment 竖直对齐方式枚举类型
HorizontalAlignment 水平对齐方式枚举
BorderBrush 边框颜色枚举类型
Visibility 可见性枚举
Opacity 不透明度,接受数值在0.0到1.0间

你可能感兴趣的:(WPF,wpf)