Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"
这句就是标记扩展
日常工作做打交道最多的控件无外乎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类控件的派生关系如下图
我们把符合某类内容模型的UI元素称为一个族,每个族用它们共同基类来命名。
特点如下:
特点如下:
特点如下:
特点如下:
本族中的元素是在UI上起装饰效果的。如可以使用Border元素为一些组织在一起的内容加个边框。如果需要组织在一起的内容能够自由缩放,则可使用ViewBox元素。
特点如下:
这两个控件最主要的功能是显示文本。
友好的用户界面离不开各种图形的搭配,Shape族元素(它们只是简单的视觉元素,不是控件)就是专门用来在UI上绘制图形的一类元素。这类元素没有自己的内容,我们可以使用Fill属性为它们设置填充效果,还可以使用Stroke属性为它们设置边线的效果。
特点如下:
所有用于UI布局的元素都属于这一族
特点如下:
Grid元素会以网格的形式对内容元素们(即它的 Children)进行布局。
特点如下:
基于这些特点,Grid适用的场合有:
Grid类具有ColumnDefinitions 和RowDefinitions两个属性,它们分别是ColumnDefinition和RowDefinition 的集合,表示Grid定义了多少列、多少行。
只定义行和列的个数还远远不够,我们还需要设置行的高度和列的宽度才能形成有意义的布局。这就引出两个问题:
宽度和高度的单位是什么。
宽度和高度可以取什么样的值。
<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>
StackPanel可以把内部元素在纵向或横向上紧凑排列、形成栈式布局,通俗地讲就是把内部元素像垒积木一样“撂起来”。垒积木大家都玩过,当把排在前面的积木块抽掉之后排在它后面的元素会整体向前移动、补占原有元素的空间。基于这个特点,StackPanel适合的场合有:
<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>
Canvas 译成中文就是“画布”,显然,在Canvas里布局就像在画布上画控件一样。使用Canvas布局与在 Windows Form窗体上布局基本上是一样的,只是在Windows Form开发时我们通过设置控件的Left和Top等属性来确定控件在窗体上的位置,而WPF的控件没有Left和Top等属性,就像把控件放在Grid里时会被附加上 Grid.Column和Grid.Row属性一样,当控件被放置在 Canvas里时就会被附加上 Canvas.X和 Canvas.Y属性。
Canvas适用的场合包括:
DockPanel内的元素会被附加上 DockPanel.Dock这个属性,这个属性的数据类型为Dock 枚举。Dock枚举可取 Left、Top、Right和 Bottom四个值。根据Dock 属性值,DockPanel内的元素会向指定方向累积、切分DockPanel内部的剩余可用空间,就像船舶靠岸一样。
DockPanel还有一个重要属性——bool类型的LastChildFill,它的默认值是True。当LastChildFill属性的值为True时,DockPanel内最后一个元素的 DockPanel.Dock 属性值会被忽略,这个元素会把DockPanel 内部所有剩余空间充满。这也正好解释了为什么Dock枚举类型没有Fill 这个值。
WrapPanel内部采用的是流式布局。WrapPanel使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment 和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPanel会排列尽可能多的控件,排不下的控件将会新起一行或一列继续排列。
需要设置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个参数是我们记忆的重点:
下面的代码是把一个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>
可以根据数据需要流通的方向设置单向、双向等等。
控制 Binding 数据流向的属性是Mode,它的类型是 BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OnTime、OneWayToSource和 Default。这里的Default值是指Binding的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text属性),Default就采用双向模式;若是只读的(如TextBlock.Text)则采用单向模式。
尽管在XAML代码中或者Binding类的构造器参数列表中我们以一个字符串来表示 Path,但Path 的实际类型是PropertyPath。下面让我们看看如何创建Path 来应对各种情况。
等效的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);
<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使用,则需要使用这样的语法:
4. 如果集合元素的属性仍然还是一个集合,我们想把子级集合中的元素当作 Path,则可以使用多级斜线的语法(即一路“斜线”下去),例如:
Binding源本身就是数据且不需要Path来说明。典型的string、int,无法指出通过哪个属性来访问这个数据,只需要将Path的值设置为“.”或干脆不写(在XAML中可以),在C#中不能省略
Binding 的源是数据的来源,所以,只要一个对象包含数据并能通过属性把数据暴露出来,它就能当作 Binding 的源来使用。包含数据的对象比比皆是,但必须为Binding 的Source指定合适的对象Binding才能正确工作,常见的办法有:
前面的例子都是把单个CLR类型对象指定为Binding 的Source,方法有两种——把对象赋值给Binding.Source属性或把对象的Name赋值给Binding.ElementName。
名称 | 含义 |
---|---|
VerticalAlignment | 竖直对齐方式枚举类型 |
HorizontalAlignment | 水平对齐方式枚举 |
BorderBrush | 边框颜色枚举类型 |
Visibility | 可见性枚举 |
Opacity | 不透明度,接受数值在0.0到1.0间 |