2009-12-02 15 views
7

Veo a varias personas diciendo que WPF puede usar "Descriptores de tipo personalizado" para "Notificación de cambio".Enlazado de datos WPF - Ejemplo de "Descriptor de tipo personalizado"

Las maneras que sé cómo hacerlo Notificación de cambio son:

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

O es que mi objetivo implementar INotifiyPropertyChanged.

Veo comentarios que dicen que los Descriptores de tipos personalizados también funcionarán, pero nadie da un buen ejemplo de cómo funciona. Ahora estoy pidiendo ese ejemplo (por ejemplo, un buen ejemplo de enlace de datos de WPF y actualización a través de descriptores de tipo personalizado).

Respuesta

20

Aquí hay un ejemplo bastante simple para ti.

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

que utiliza el ejemplo excelente y muy claro por Kent Boogart como base para mis tipos personalizados.

Tengo algunos pequeños cambios que creo que deberían hacerse al programa de ejemplo para aclarar la relación entre CustomTypeDescriptor y PropertyDescriptor.

  1. Creo que los datos deben almacenarse en la instancia del objeto de tipo, no en los descriptores de propiedad.
  2. Por lo general, esperaría que cada instancia de tipo personalizado conservara su propia colección de descriptores de propiedades, en lugar de que sea estática. Para aclarar esto, he agregado más información (a Type) para escribir el descriptor de propiedad.

El segundo punto es realmente un problema de dominio, pero esperaría que un uso más típico requiriera datos de propiedad de instancia, ya que uno usa ese tipo cuando las propiedades no se conocen en tiempo de compilación.

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); 
      } 
     } 
    } 
} 

Espero no haber hecho ningún aullador, ¡ya que esta es mi primera publicación!

Cuestiones relacionadas