2009-12-02 59 views
7

我看到几个人说WPF可以使用“自定义类型描述符”作为“更改通知”。WPF数据绑定 - “自定义类型描述符”示例

我知道该怎么做更改通知的方法是:

object.GetBindingExpression(Bound.property).UpdateTarget(); 

还是有我的目标实现INotifiyPropertyChanged

我看到评论说自定义类型描述符也可以工作,但没有人给出一个很好的例子。我现在要求这个例子(IE是WPF数据绑定和通过定制类型描述符更新的一个很好的例子。)

回答

20

这是一个非常简单的例子。

Window1.xaml

<Window x:Class="CTDExample.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300"> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="Auto"/> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 

     <TextBlock>Name:</TextBlock> 
     <TextBox Grid.Column="1" Text="{Binding Name}"/> 

     <TextBlock Grid.Row="1">Age:</TextBlock> 
     <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Age}"/> 

     <TextBlock Grid.Row="2" Grid.ColumnSpan="2"> 
      <TextBlock.Text> 
       <MultiBinding StringFormat="{}{0} is {1} years old."> 
        <Binding Path="Name"/> 
        <Binding Path="Age"/> 
       </MultiBinding> 
      </TextBlock.Text> 
     </TextBlock> 
    </Grid> 
</Window> 

Window1.xaml.cs

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows; 

namespace CTDExample 
{ 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 

      var ctd = new CTD(); 
      ctd.AddProperty("Name"); 
      ctd.AddProperty("Age"); 
      DataContext = ctd; 
     } 
    } 

    public class CTD : CustomTypeDescriptor 
    { 
     private static readonly ICollection<PropertyDescriptor> _propertyDescriptors = new List<PropertyDescriptor>(); 

     public void AddProperty(string name) 
     { 
      _propertyDescriptors.Add(new MyPropertyDescriptor(name)); 
     } 

     public override PropertyDescriptorCollection GetProperties() 
     { 
      return new PropertyDescriptorCollection(_propertyDescriptors.ToArray()); 
     } 

     public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
     { 
      return GetProperties(); 
     } 

     public override EventDescriptorCollection GetEvents() 
     { 
      return null; 
     } 

     public override EventDescriptorCollection GetEvents(Attribute[] attributes) 
     { 
      return null; 
     } 
    } 

    public class MyPropertyDescriptor : PropertyDescriptor 
    { 
     private readonly IDictionary<object, object> _values; 

     public MyPropertyDescriptor(string name) 
      : base(name, null) 
     { 
      _values = new Dictionary<object, object>(); 
     } 

     public override bool CanResetValue(object component) 
     { 
      throw new NotImplementedException(); 
     } 

     public override Type ComponentType 
     { 
      get { throw new NotImplementedException(); } 
     } 

     public override object GetValue(object component) 
     { 
      object value = null; 
      _values.TryGetValue(component, out value); 
      return value; 
     } 

     public override bool IsReadOnly 
     { 
      get { return false; } 
     } 

     public override Type PropertyType 
     { 
      get { return typeof(object); } 
     } 

     public override void ResetValue(object component) 
     { 
      throw new NotImplementedException(); 
     } 

     public override void SetValue(object component, object value) 
     { 
      var oldValue = GetValue(component); 

      if (oldValue != value) 
      { 
       _values[component] = value; 
       OnValueChanged(component, new PropertyChangedEventArgs(base.Name)); 
      } 
     } 

     public override bool ShouldSerializeValue(object component) 
     { 
      throw new NotImplementedException(); 
     } 

     public override void AddValueChanged(object component, EventHandler handler) 
     { 
      // set a breakpoint here to see WPF attaching a value changed handler 
      base.AddValueChanged(component, handler); 
     } 
    } 
} 
5

我用出色的,非常明显的例子,通过Kent Boogart作为我的自定义类型的基础。

我认为应该对示例程序进行一些小的更改以阐明CustomTypeDescriptorPropertyDescriptor之间的关系。

  1. 我相信数据应该存储在类型对象的实例中,而不是属性描述符。
  2. 通常我会希望每个自定义类型实例保留它自己的属性描述符集合,而不是静态的。为了澄清这一点,我添加了一些更多的信息(一个Type)来键入属性描述符。

第二点实际上是一个域问题,但我认为更典型的用法需要实例属性数据,因为在编译时不知道属性的情况下使用这种类型。

MainWindow.xaml

<Window 
    x:Class="CTDExample.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="350" Width="525"> 

    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="Auto"/> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 

     <TextBlock>Name:</TextBlock> 
     <TextBox Grid.Column="1" Text="{Binding Name}"/> 

     <TextBlock Grid.Row="1">Age:</TextBlock> 
     <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Age}"/> 

     <TextBlock Grid.Row="2" Grid.ColumnSpan="2"> 
      <TextBlock.Text> 
       <MultiBinding StringFormat="{}{0} is {1} years old."> 
        <Binding Path="Name"/> 
        <Binding Path="Age"/> 
       </MultiBinding> 
      </TextBlock.Text> 
     </TextBlock> 
    </Grid> 
</Window> 

MainWindow.xaml.cs

using System.Windows; 

namespace CTDExample 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 

      var ctd = new MyCustomType(); 
      ctd.AddProperty("Name", typeof(string)); // Now takes a Type argument. 
      ctd.AddProperty("Age", typeof(int)); 
      DataContext = ctd; 
     } 
    } 
} 

MyCustomType.cs

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 

namespace CTDExample 
{ 
    public class MyCustomType : CustomTypeDescriptor 
    { 
     // This is instance data. 
     private readonly ICollection<PropertyDescriptor> _propertyDescriptors = new List<PropertyDescriptor>(); 

     // The data is stored on the type instance. 
     private readonly IDictionary<string, object> _propertyValues = new Dictionary<string, object>(); 

     // The property descriptor now takes an extra argument. 
     public void AddProperty(string name, Type type) 
     { 
      _propertyDescriptors.Add(new MyPropertyDescriptor(name, type)); 
     } 

     public override PropertyDescriptorCollection GetProperties() 
     { 
      return new PropertyDescriptorCollection(_propertyDescriptors.ToArray()); 
     } 

     public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
     { 
      return GetProperties(); 
     } 

     public override EventDescriptorCollection GetEvents() 
     { 
      return null; 
     } 

     public override EventDescriptorCollection GetEvents(Attribute[] attributes) 
     { 
      return null; 
     } 

     private class MyPropertyDescriptor : PropertyDescriptor 
     { 
      // This data is here to indicate that different instances of the type 
      // object may have properties of the same name, but with different 
      // characteristics. 
      private readonly Type _type; 

      public MyPropertyDescriptor(string name, Type type) 
       : base(name, null) 
      { 
       _type = type; 
      } 

      public override bool CanResetValue(object component) 
      { 
       throw new NotImplementedException(); 
      } 

      public override Type ComponentType 
      { 
       get { throw new NotImplementedException(); } 
      } 

      public override object GetValue(object component) 
      { 
       MyCustomType obj = (MyCustomType)component; 
       object value = null; 
       obj._propertyValues.TryGetValue(Name, out value); 
       return value; 
      } 

      public override bool IsReadOnly 
      { 
       get { return false; } 
      } 

      public override Type PropertyType 
      { 
       get { return _type; } 
      } 

      public override void ResetValue(object component) 
      { 
       throw new NotImplementedException(); 
      } 

      public override void SetValue(object component, object value) 
      { 
       var oldValue = GetValue(component); 

       if (oldValue != value) 
       { 
        MyCustomType obj = (MyCustomType)component; 
        obj._propertyValues[Name] = value; 
        OnValueChanged(component, new PropertyChangedEventArgs(Name)); 
       } 
      } 

      public override bool ShouldSerializeValue(object component) 
      { 
       throw new NotImplementedException(); 
      } 

      public override void AddValueChanged(object component, EventHandler handler) 
      { 
       // set a breakpoint here to see WPF attaching a value changed handler 
       base.AddValueChanged(component, handler); 
      } 
     } 
    } 
} 

我希望我没有做过任何吹嘘,因为这是我的第一篇文章!