首发于 C#

深入浅出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"/>