WPF Data Binding - Example of “Custom Type Descriptor”

前端 未结 2 852

I see several people saying that WPF can use \"Custom Type Descriptors\" for \"Change notification\".

The ways I know how to do Change Notification are:



        
相关标签:
2条回答
  • 2020-12-13 17:09

    I used the excellent and very clear example by Kent Boogart as the basis for my custom types.

    I have a few minor changes I think should be made to the example program to clarify the relationship between the CustomTypeDescriptor and the PropertyDescriptor.

    1. I believe the data should be stored on the instance of the type object, not the property descriptors.
    2. Typically I would expect each custom type instance to keep it's own collection of property descriptors, rather than this being static. To clarify this I has added some more information (a Type) to type property descriptor.

    The second point is really a domain issue, but I would expect a more typical use would require instance property data, as one uses such a type when the properties are not known at compile time.

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

    I hope I haven't made any howlers, as this is my first post!

    0 讨论(0)
  • 2020-12-13 17:18

    Here's a pretty simple example for you.

    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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题