深入浅出WPF
WPF 数据驱动程序运行,属于“内容决定形式”的思维方式
XAML(Extensible Application Markup Language),使用xmlns(XML Namespace的缩写)特征来定义命名空间,xmlns后可以跟一个可选的映射前缀,之间用冒号分割,没有可选的映射前缀的名称空间成为“默认名称空间”,默认名称空间只能有一个。
第2~5行看起来像网页地址的名称空间是XAML解析器的一个硬性编码,并不是真实的网页,解析器只要见到这些固定的字符串,解析器就会把一系列必要的程序集(Assembly)和程序集中包含的.NET名称空间引用进来。
http://schemas.microsoft.com/winfx/2006/xaml/presentation 对应的是与绘制UI相关的程序集, http://schemas.microsoft.com/winfx/2006/xaml 对应XAML语言解析处理相关的程序集。
x:Class 特征的作用是:当XAML解析器将包含它的标签解析成C#类后的类的类名,这样就知道该部分代码与哪个类代码进行合并。
属性元素 :指的是某个标签的一个元素(子标签)对应这个标签的一个属性,即以元素的形式来表达实例的属性。
<Rectangle Name="rect" HorizontalAlignment="Left" Height="100" Margin="110,190,0,0" Stroke="Black" VerticalAlignment="Top" Width="100">
<Rectangle.Fill>
<SolidColorBrush Color="Blue"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
标记扩展 :标记扩展实际上是一种特殊的Attribute=Value的语法,Value字符串是由一对花括号括起来的内容组成,XAML编译器会对这样的内容进行解析,生成相应的对象。
//将Textbox的text和Slider的值绑定
<TextBox x:Name="textbox1" Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" HorizontalAlignment="Left" Margin="335,275,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Slider x:Name="slider1" HorizontalAlignment="Left" Margin="325,170,0,0" VerticalAlignment="Top" Width="120"/>
事件处理器:标签的Attribute一部分会对应这个对象的Property,还有一部分Attribute对应对象的事件,例如<Button>标签的有一个名为Click的Attribute,对应的就是Button类的Click事件。
代码后置:将逻辑代码和UI分离,.NET通过patial关键字支持类分开定义,将解析XAML所生成的代码与x:Class所指的的类进行合并。
x:Code标签可将本应该保存在后置C#代码搬到XAML文件里:
XAML注释格式:<!--这是注释-->
x名称空间:x名称空间映射的是 http://schemas.microsoft.com/winfx/2006/xaml ,其中包含的类均与解析XAML语言相关,XAML会被解析编译形成微软中间语言存储在程序集中。
x名称空间中的Attribute:
x:Class 告诉XAML编译器将XAML标签的编译结果与C#后台代码指定的类合并
x:ClassModifier 告诉XAML编译器由XAML标签生成的类的访问控制级别,其值应和x:Class指定的类的访问级别一致。
x:Name 告诉编译器标签对应实例的名称,Name属性被设置为x:Name的值并注册到UI树上以方便查找。
x:FieldModifier 在XAML里改变引用变量的访问级别
x:Key 在XAML里可以把需要多次使用的内容提取出来放在资源字典里,是要使用时使用Key进行检索
在后台C#代码中也可以使用该资源 string str =this.FindResource("myString") as string;
x:Shared 与x:Key配合使用,如果x:Shared设置为true,则每次检索资源对象是得到的是同一个对象,否则是对象的新副本。默认x:Shared值为true
x名称空间中的标记扩展:
x:Type 数据类型名称
x:Null 表示XAML中的空值
x:Array 通过其Items属性像使用者提供一个类型已知的ArrayList实例,类型由x:Array的Type指明
<ListBox Margin="5">
<ListBox.ItemsSource>
<x:Array Type="sys:String">
<sys:String>Tom</sys:String>
<sys:String>Jack</sys:String>
<sys:String>Altman</sys:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
x:Static 在XAML中使用数据类型的static成员
//类中定义静态字段或属性
public static string WindowTitle = "TITLE";
<TextBox x:Name="textbox1" Text="{x:Static local:MainWindow.WindowTitle}" HorizontalAlignment="Left" Margin="335,275,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
UI控件
XAML标签的内容区域专门映射了控件的内容属性。我们把符合某类内容模型的UI元素称为一个族,每个族用其共同基类来命名。
ContentControl族 :内容属性名称为Content,只能由单一元素充当其内容
HeaderedContentControl族 :控件有一个内容区域和显示标题的区域,Content和Header都只能容纳一个元素。
ItemsControl族 :用于显示列表化的数据,内容属性为Items或ItemsSource
HeaderedItemsControl族 :用于显示列表化的数据,同时显示一个标题。包括MenuItem,TreeViewItem和ToolBar
Decorator族 :在UI上起装饰效果的,内容属性为Child,只能由单一元素充当其内容
TextBlock和TextBox : TextBlock只能显示文本,不能编辑,又称为静态文本,可以使用丰富的格式控制标记,内容属性为Inlines或Text;TextBox内容是简单的字符串,可编辑,内容属性为Text
Shape族 :是简单的视觉元素不是控件,用于2D图形绘制,无内容属性
Panel族 :派生自Panel抽象类,用于控制UI布局,内容属性为Children
Grid网格:
RowDefinitions和ColumnDefinitions定义行和列,宽度和高度数值后加*表示为比例值,Grid.Row设置控件所在行,Grid.Column设置控件所在列,Grid.ColumnSpan设置列跨度,Grid.RowSpan设置行跨度
StackPanel栈式面板:将元素在横向或者纵向排列成栈式布局
Canvas画布:内部元素可以用像素坐标为单位的绝对坐标进行定位
DockPanel停靠面板:内部的元素会被附加上DockPanel.Dock属性,DockPanel的LastChildFill属性设置最有一个元素是否填充剩余的空间。
WrapPanel自动折行面板,使用Orientation属性设置流延伸的方向,
Binding关联
Binding源是逻辑层的对象,目标是UI层的控件对象,从而实现数据驱动UI
class Student:INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
get { return name; }
name = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
private Student stu = new Student();
public MainWindow()
InitializeComponent();
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu = new Student() });
控件之间绑定,例如Slider绑定到Textbox:
<TextBox x:Name="textBox1" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="5" BorderBrush="Blue" Text="{Binding Path=Value, ElementName=slider1}"></TextBox>
控制Binding数据流向的属性是Mode,其类型是BindingMode枚举,枚举值包括TwoWay,OneWay,OnTime,OneWayToSource和Default。Binding还有NotifyOnSourceUpdated和NotifyOnTragetUpdated两个bool类型的属性,如果设置为true,则当源或目标发生更新后Binding会激发相应的SourceUpdated事件和TargetUpdated事件。
作为绑定的源可能有很多属性,需要使用Path来指定绑定哪个属性,Path的实际数据类型为PropertyPath,如果Binding源本身就是数据则不需要用Path来指明,例如string,int等基本类型,他们的实例本身就是数据,此时只需要将Path的值设置为“.”就可以了,在XAML中“.”可以省略不写,但在C#代码中则不能省略。
<Window.Resources>
<sys:String x:Key="myString">Hello WPF Reource</sys:String>
</Window.Resources>
<TextBox x:Name="textBox2" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="30,0,0,0" Grid.Row="4" TextWrapping="Wrap" VerticalAlignment="Center" Width="120"/>
对于没有指定Source的Binding,使用DataContext作为Binding的源。 DataContext(数据上下文)属性 定义在FrameworkElement类里,这个类是WPF控件的基类,当Binding未指定源时会沿着UI元素树一路向根部找过去,每路过一个结点就看看这个结点的DataContext是否具有Path所指定的属性,如果没有的话会继续找下去。
class Student
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Student(string id, string name, int age)
Id = id;
Name = name;
Age = age;
List<Student> students = new List<Student>();
students.Add(new Student("001","Tom",20));
students.Add(new Student("002", "Larry", 30));
students.Add(new Student("005", "Jim", 40));
this.listBox1.ItemsSource = students;
this.listBox1.DisplayMemberPath = "Name";
//listBox选中项的Id关联到textBox的Text上
this.textBox1.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Id") { Source = this.listBox1 });
使用XmlDataProvider可以把XML数据指定为Source,ObjectDataProvider把对象作为数据源提供给Binding,这两个类的父类都是DataSourceProvider抽象类
数据校验
Binding的ValidationRules属性用于数据校验,ValidationRule是个抽象类,使用时需要创建其派生类并重写其Validate方法。Binding进行校验时默认只校验从Target到Source的数据,从Source到Target的数据部进行校验。如果想校验从Source到Target的数据,需将校验条件的ValidatesOnTargetUpdated设置为true.
public partial class Window1 : Window
public Window1()
InitializeComponent();
Binding binding = new Binding("Value") { Source = slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
var rule = new RangeValidateRule();
rule.ValidatesOnTargetUpdated = true; //从Source到Target的数据也进行校验
binding.ValidationRules.Add(rule);
binding.NotifyOnValidationError = true; //设置路由
this.textBox1.SetBinding(TextBox.TextProperty, binding);
this.textBox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(ValidationError));
//路由事件处理程序
private void ValidationError(object sender, RoutedEventArgs e)
if(Validation.GetErrors(this.textBox1).Count>0)
this.textBox1.ToolTip =Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
public class RangeValidateRule : ValidationRule
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
double d = 0;
if(double.TryParse(value.ToString(), out d))
if (d >= 0 && d <= 100) return new ValidationResult(true, null);
return new ValidationResult(false, "校验失败");
数据类型转换
Binding的Converter属性用于数据类型转换,创建一个类并实现IValueConverter接口。
<ListBox x:Name="listBoxPlane" Width="200" Height="100">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Width="80" TextAlignment="Center"></TextBlock>
<CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stb}}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public enum State
Locked,
Available,
Unknown,
public class Plane
public string Name { get; set; }
public State State { get; set; }
public class StateToNullBoolConverter : IValueConverter
//State转bool?
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
State state = (State)value;
switch (state)
case State.Locked:
return false;
case State.Available:
return true;
case State.Unknown:
default:
return null;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
bool? nb =(bool?)value;
switch(nb)
case true:
return State.Available;
case false:
return State.Locked;
case null:
default:
return State.Unknown;
//设置数据源
List<Plane> planes = new List<Plane>()
new Plane() { Name = "AAA", State = State.Locked },
new Plane() { Name = "CCC", State = State.Available },
new Plane() { Name = "BBB", State = State.Unknown }
this.listBoxPlane.ItemsSource = planes;
多路绑定
当需要多个数据来源时,需要使用MultiBinding,其具有一个名为Bindings的属性
public partial class Window1 : Window
public Window1()
InitializeComponent();
Binding binding1 = new Binding("Text") { Source = this.textBox1 };
Binding binding2 = new Binding("Text") { Source = this.textBox2 };
Binding binding3 = new Binding("Text") { Source = this.textBox3 };
Binding binding4 = new Binding("Text") { Source = this.textBox4 };
MultiBinding mb = new MultiBinding();
mb.Bindings.Add(binding1);
mb.Bindings.Add(binding2);
mb.Bindings.Add(binding3);
mb.Bindings.Add(binding4);
mb.Converter = new LogonMultiBindingConverter();
//TextBox1和TextBox2内容相同,TextBox3和TextBox4内容相同时,button使能
this.btn1.SetBinding(Button.IsEnabledProperty, mb);
public class LogonMultiBindingConverter : IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
if(!values.Cast<string>().Any(text=>string.IsNullOrWhiteSpace(text)) &&
values[0].ToString() == values[1].ToString() &&
values[2].ToString() == values[3].ToString()
return true;
return false;
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
throw new NotImplementedException();
依赖属性
依赖属性是一种可以自己没有值,能通过使用Binding从数据源获得值的属性,拥有依赖属性的对象称为“依赖对象”。WPF中依赖对象的概念由DependencyObject类实现,依赖属性的概念由DependencyProperty类所实现,要想使用依赖属性,宿主一定是DenpendencyObject的派生类。使用DependencyProperty声明的变量使用public static readonly进行修饰。
public class Student:DependencyObject
public int Age
get { return (int)GetValue(AgeProperty); }
set { SetValue(AgeProperty, value); }
// Using a DependencyProperty as the backing store for Age. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AgeProperty =
DependencyProperty.Register("Age", typeof(int), typeof(Student), new PropertyMetadata(0));
//CLR属性包装器
public string Name
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
//依赖属性
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(Student));
//SetBinding包装
public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding)
return BindingOperations.SetBinding(this, dp, binding);
在vs中输入propdp然后按两次Tab键,自动创建依赖属性和其对应的CLR属性。
DependencyProperty.Register()方法创建DependencyProperty实例并且对其进行“注册”。DependencyProperty类有一个名为PropertyFromName的全局哈希表,这个哈希表就是用来注册依赖属性的地方,hash code为第一个参数(CLR属性名字符串)的hash code与第三个参数(宿主类型)的hash code进行异或运算得到的。
DependencyProperty实例的哈希值为GlobalIndex属性值,即通过GlobalIndex属性值就可以检索到某个DependencyProperty实例,而hash code由其CLR包装器名和宿主类型名共同决定,为了保证GlobalIndex属性值的稳定性,声明依赖属性时使用readonly关键字进行修饰。
附加属性(Attached Property)
把对象放入一个特定环境才具有的属性(表现出来就是被环境所赋予的属性)称为附加属性。附加属性的本质就是依赖属性,二者仅在注册和包装器上有一些区别,在VS中使用 propa 提示符快速创建附加属性。
例如将TextBox放入Grid中,那么TextBox就有了Grid.Row和Grid.Column属性。
class School: System.Windows.DependencyObject
public static int GetGrade(DependencyObject obj)
return (int)obj.GetValue(GradeProperty);
public static void SetGrade(DependencyObject obj, int value)
obj.SetValue(GradeProperty, value);
// Using a DependencyProperty as the backing store for Grade. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GradeProperty =
DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new UIPropertyMetadata(0));
class Human:DependencyObject
Human human = new Human();
School.SetGrade(human, 6);
int grade = School.GetGrade(human);
路由事件
WPF中有两种“树”,一种叫做逻辑树(Logical Tree),一种叫做可视化树(Visual Tree).逻辑树完全由布局组件和控件构成,而Visual Tree则包括了控件的内部结构。可以借助LogicalTreeHelper类来在LogicalTree上导航或查找元素,借助VisualTreeHelper在Visual Tree上导航或查找元素。
为了降低传统.NET中由事件订阅带来的耦合度和代码量,WPF推出了路由事件机制,路由事件与直接事件的区别在于,直接事件激发时,事件发送者直接将消息通过事件订阅交给事件响应者,事件响应者利用其事件处理方法对事件做出响应。路由事件的事件拥有者和事件响应者之间没有显式的订阅关系,事件拥有者只负责激发事件,事件由谁响应它并不知道。事件响应者安装有事件侦听器,针对 某类事件 进行侦听,例如侦听Button的Click事件,那么侦听者不关心是哪个Button的Click事件被传来,任何一个Button的Click都会被侦听到。
this.stackpanel1.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));
private void Button_Click(object sender, RoutedEventArgs e)
MessageBox.Show((e.OriginalSource as FrameworkElement).Name); //事件的源头通过e.OriginalSource找到,而不是sender
路由事件的消息包含在RoutedEventArgs实例中,RoutedEventArgs有两个属性Source和OriginalSource,这两个属性都表示路由事件传递的起点, 只不过Source表示的是LogicalTree上的消息源头,而OriginalSource则表示VisualTree上的源头 。RoutedEventArgs类具有一个bool类型的属性Handled,设置为true表示事件已经被处理了,不会向下再传递了。
路由事件策略:
- Bubbling【冒泡】:调用事件源上的事件处理程序,路由事件随后会路由到后续的父级元素,直到到达元素树的根。
- Tunneling【隧道】:最初将调用元素树的根处的事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。事件是以Preview开头。隧道事件有时又称作预览事件
- Direct【直接】: 只有源元素本身才有机会调用处理程序以进行响应。
命令
命令与事件的区别是命令具有约束力
WPF的命令系统由几个基本要素组成:
命令(Command):是实现了ICommand接口的类,使用的最多的是RoutedCommand类
命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类。
命令目标(Command Target):即命令发送给谁,是实现了IInputElement接口的类
命令关联(Command Binding):负责把一些外围逻辑和命令关联起来
public partial class Window1 : Window
private RoutedCommand clearCmd =new RoutedCommand("Clear",typeof(Window1));
public Window1()
InitializeComponent();
InitCommand();
private void InitCommand()
//设置命令手势
this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
//设置命令源和目标
this.btn1.Command = this.clearCmd;
this.btn1.CommandTarget = this.textBox1;
CommandBinding cb = new CommandBinding();
cb.Command = this.clearCmd;
cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
//CommandBinding设置在命令目标的外围控件上
this.stackPanel1.CommandBindings.Add(cb);
//当探测命令是否可以执行时,此方法被调用
void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
if(string.IsNullOrEmpty(this.textBox1.Text))
e.CanExecute = false;
e.CanExecute = true;
e.Handled = true;
//当命令送达目标后,此方法被调用
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
this.textBox1.Clear();
e.Handled = true;
微软在WPF类库中定义了一些便捷的命令库,包括ApplicationCommands,ComponnetCommands,NavigationCommands,MediaCommands和EditingCommands。对于同一个命令,可以使用CommandParameter区分参数对象。
<StackPanel x:Name="stackPanel1">
<TextBox x:Name="textBox1" Margin="10"></TextBox>
<Button x:Name="btn1" Content="Button1" Height="41" Command="New" CommandParameter="Teacher"></Button>
<Button x:Name="btn2" Content="Button2" Height="40" Command="New" CommandParameter="Student"></Button>
<ListBox x:Name="listBox1" Height="100" Background="Blue"></ListBox>
</StackPanel>
<Window.CommandBindings>
<CommandBinding Command="New" CanExecute="cb_CanExecute" Executed="cb_Executed">
</CommandBinding>
</Window.CommandBindings>
//当探测命令是否可以执行时,此方法被调用
void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
if(string.IsNullOrEmpty(this.textBox1.Text))
e.CanExecute = false;
e.CanExecute = true;
e.Handled = true;
//当命令送达目标后,此方法被调用
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
string name =this.textBox1.Text;
if(e.Parameter.ToString()== "Teacher")
this.listBox1.Items.Add(string.Format("New teacher:{0}", name));
else if (e.Parameter.ToString() == "Student")
this.listBox1.Items.Add(string.Format("New Student:{0}", name));
e.Handled = true;
}
资源
通过xmlns:sys="clr-namespace:System;assembly=mscorlib"引入System名称空间,
<Window.Resources>
<sys:String x:Key="str">
hello world
</sys:String>
<sys:Double x:Key="dbl">3.1415926</sys:Double>
</Window.Resources>
<StackPanel x:Name="stackpanel1" HorizontalAlignment="Left" Height="100" Margin="82,45,0,0" VerticalAlignment="Top" Width="232">
<TextBox x:Name="textbox1" Height="23" TextWrapping="Wrap" Text="{StaticResource str}"/>
</StackPanel>
在C#代码中使用资源 string text =(string)this.FindResource("str");
在检索资源时,首先查找控件自己的Resources属性,如果没有这个资源程序会沿着逻辑树向上一级控件查找,如果连最顶级控件也没有这个资源,程序会去查找Application.Resources,如果还是没找到就抛出异常。尽管每个元素都提供了Resources属性,但通常在窗口级别上定义资源。
使用StaticResource引用静态资源,使用DynamicResource引用动态资源。对于静态资源在第一次创建窗口时,一次性地设置完毕,之后就不再访问这个资源了;而对于动态资源,如果发生了改变,则会重新应用资源。
ResourceDictionary有一个名为Source的属性,只要将资源文件路径赋值给这个属性就可以引用这个资源。
可将资源定义在资源字典文件中,右键项目添加资源字典。使用资源字典,需要将其合并到应用程序中资源集合位置。
<Application.Resources>
<!--合并资源字典到Application.Resources中-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
使用pack uri访问二进制资源
//C#代码
Uri imgUri =new Uri(@"Resource/image/claudia.png",UriKind.Relative);
this.image1.Source =new BitmapImage(imgUri);
//或者XAML中
<Image x:Name="image1" Source="Resource/image/claudia.png"> </Image>
模板
WPF中的Template分为两大类:ControlTemplate和DataTemplate, 其中DataTemplate决定数据外观,ControlTemplate决定控件外观 。
DataTemplate
DataTemplate常用的地方有3处,分别是ContentControl的ContentTemplate属性,ItemsControl的ItemTemplate属性,GridViewColumn的CellTemplate属性。
<Window x:Class="Wpf_DataTemplate.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:Wpf_DataTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Employee}">
<Border Padding="5" BorderThickness="2" BorderBrush="Blue" CornerRadius="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="txtName" VerticalAlignment="Center" Text="{Binding Name}" FontSize="18" FontWeight="Black"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding Job}"/>
<TextBlock Text="{Binding Department}"/>
<TextBlock Text="{Binding Email}"/>
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Sex}" Value="Male">
<Setter TargetName="txtName" Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Sex}" Value="Female">
<Setter TargetName="txtName" Property="Foreground" Value="LightGreen"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ListBox x:Name="listBox1" ></ListBox>
</Grid>
</Window>
C#端的代码:
public partial class MainWindow : Window
private List<Employee> Employees = new List<Employee>();
public MainWindow()
InitializeComponent();
Employees = new List<Employee>()
new Employee() {Name="张明",Department="技术售后部",Job="售后工程师",Sex=Sex.Male,Email="Zhang.San@wpf.com"},
new Employee() {Name="葛倩",Department="人事部",Job="招聘专员",Sex=Sex.Female,Email="Ge.Qian@wpf.com"},
new Employee() {Name="王小伟",Department="研发部",Job="高级软件工程师",Sex=Sex.Male,Email="Wang.Xiaowei@wpf.com"},
this.listBox1.ItemsSource = Employees;
public class Employee
public string Name { get; set; }
public Sex Sex { get; set; }
public string Job { get; set; }
public string Department { get; set; }
public string Email { get; set; }
public enum Sex
Male,
Female
运行效果:
ControlTemplate修改控件的外观,在Blend中选中控件右键【编辑模板】-》【编辑副本】。
ItemsControl有一个名为ItemsPanel的属性,它的数据类型为ItemsPanelTemplate,可以来控制其条目容器,例如可以将其元素横向排列。
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<TextBlock Text="111"></TextBlock>
<TextBlock Text="222"></TextBlock>
<TextBlock Text="333"></TextBlock>
</ListBox>
如果将某个Style应用到所有目标上,则Stype不进行x:Key标记,且设置其x:Type,如果某个控件不想使用这个Style,可以将该控件的Style属性设置为{x:Null}
获取ControlTemplate内部控件:
<Window.Resources>
<ControlTemplate x:Key="cTmp">
<StackPanel Background="Orange">
<TextBox x:Name="textbox1" Margin="6"></TextBox>
<TextBox x:Name="textbox2" Margin="6"></TextBox>
<TextBox x:Name="textbox3" Margin="6"></TextBox>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<UserControl x:Name="uc" Template="{StaticResource ResourceKey=cTmp}" Margin="5"></UserControl>
<Button x:Name="btn1" Click="btn1_Click" Height="50" Content="Button"></Button>
</StackPanel>
private void btn1_Click(object sender, RoutedEventArgs e)
TextBox tb1 = this.uc.Template.FindName("textbox1", this.uc) as TextBox;
tb1.Text = "aaa";
StackPanel sp = tb1.Parent as StackPanel;
(sp.Children[1] as TextBox).Text = "bbb";
(sp.Children[2] as TextBox).Text = "ccc";
}
Style
构成style的最重要的l两种元素2是Setter和Trigger,其中Setter用于设置控件的静态外观风格,Trigger用于设置控件的行为风格。
<Style TargetType="TextBlock">
<Style.Setters>
<Setter Property="FontSize" Value="24"></Setter>
<Setter Property="TextDecorations" Value="Underline"></Setter>
<Setter Property="FontStyle" Value="Italic"></Setter>
</Style.Setters>
</Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Foreground" Value="Orange"></Setter>
</Trigger>
</Style.Triggers>
</Style>
MultiTrigger比Trigger多了个Conditions属性,多个条件成立时才会被触发。EventTrigger由事件进行触发。
<Style TargetType="CheckBox">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsChecked" Value="True"></Condition>
<Condition Property="Content" Value="aaa"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Foreground" Value="Orange"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
<DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
绘图和动画
基本图形包括Line,Rectangle,Ellipse,Polygon,Polyline,Path.
Stroke(笔触)的数据类型是画刷Brush,常用的画刷类型有SolidColorBrush实心画刷,LinearGradientBrush线性渐变画刷,RadialGradientBrush径向渐变画刷,ImageBrush图像内容画刷,DrawingBrush,VisualBrush.
Path的Data属性是Geometry类,Geometry类是个抽象类
<Path Stroke="Blue" StrokeThickness="2">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="0,0">
<LineSegment Point="150,0"/>
<LineSegment Point="150,30"/>
<LineSegment Point="90,30"/>
<LineSegment Point="90,150"/>
<LineSegment Point="60,150"/>