WPF数据绑定 - “自定义类型描述符”的示例

时间:2009-12-02 17:21:40

标签: c# wpf data-binding binding

我看到有几个人说WPF可以使用“自定义类型描述符”来表示“更改通知”。

我知道如何更改通知的方式是:

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

或让我的对象实现INotifiyPropertyChanged

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

2 个答案:

答案 0 :(得分: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);
        }
    }
}

答案 1 :(得分:7)

我使用Kent Boogart的优秀且非常清晰的示例作为我的自定义类型的基础。

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

  1. 我认为数据应存储在类型对象的实例上,而不是属性描述符。
  2. 通常我希望每个自定义类型实例都保留它自己的属性描述符集合,而不是静态的。为了澄清这一点,我添加了一些更多信息(Type)来输入属性描述符。
  3. 第二点实际上是域问题,但我希望更典型的用法需要实例属性数据,因为在编译时不知道属性时会使用这种类型。

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

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