Привязка данных WPF - пример «дескриптора пользовательского типа» - PullRequest
8 голосов
/ 02 декабря 2009

Я вижу, что несколько человек говорят, что WPF может использовать "дескрипторы пользовательских типов" для "Уведомления об изменении".

Я знаю, как сделать уведомление об изменении:

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

Или мой объект реализует INotifiyPropertyChanged.

Я вижу комментарии о том, что дескрипторы пользовательских типов тоже будут работать, но никто не дает хорошего примера того, как это работает. Сейчас я прошу этот пример (IE хороший пример связывания и обновления данных WPF с помощью пользовательских дескрипторов типов.)

Ответы [ 2 ]

21 голосов
/ 03 декабря 2009

Вот довольно простой пример для вас.

* ** 1002 1003 * 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);
        }
    }
}
7 голосов
/ 03 октября 2012

Я использовал превосходный и очень четкий пример Кент Бугарт в качестве основы для своих пользовательских типов.

У меня есть несколько небольших изменений, которые, я думаю, должны быть внесены в пример программы, чтобы прояснить отношения между CustomTypeDescriptor и PropertyDescriptor.

  1. Я считаю, что данные должны храниться в экземпляре объекта типа, а не в дескрипторах свойств.
  2. Обычно я ожидал бы, что каждый экземпляр пользовательского типа будет хранить свою собственную коллекцию дескрипторов свойств, а не статичную. Чтобы прояснить это, я добавил еще немного информации (a Type) в дескриптор свойства 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);
            }
        }
    }
}

Надеюсь, я не кричал, потому что это мой первый пост!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...