2009-12-02 61 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); 
      } 
     } 
    } 
} 

我希望我沒有做過任何吹噓,因爲這是我的第一篇文章!