Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

WPF: Привязка для свойств хороших от свойств зависимостей

Anna | 17.06.2014 | нет комментариев

Вступление

WPF — восхитительная спецтехнология, которую, не смотря на все ее недочеты, дюже люблю. Тем не менее, Зачастую доводится писать не разметку, а код, тот, что помогает первой трудиться как нужно. Хотелось бы этого чураться и писать чистый XAML, но до сих пор ни одно мое приложение труднее простого не обходилось без разных хелперов (классов-помощников), написанных на C#. К счастью, есть распространенные случаи, где дозволено одним хелпером решить сразу группу задач.

Речь ниже пойдет о привязке в обыкновенных свойствах визуальных элементов, которые не являются свойствами зависимостей (dependecy properties). Штатными средствами WPF этого сделать не получится. Ко каждому прочему, мы не можем узнать об изменениях такого свойства, помимо как подписавшись на особое событие, что противоречит образцу MVVM. Такие события для всякого свойства могут быть свои. Самый общеизвестный пример — это PasswordBox и его качество Password. Так у нас сделать не получится:

<PasswordBox Password={Binding OtherProperty} />

Не будем вдаваться в подробности, для чего разработчики PasswordBox не позволили привязываться к свойству пароля. Подумаем, что здесь дозволено сделать.

Если вам не хочется читать мои рассуждения, то в конце статьи опубликован финальный код и ссылка на Github.

Определяемся со методом реализации

И так! Хотелось бы такое решение, Дабы в обращении походило на обыкновенную привязку, но с некоторыми дополнительными параметрами. Также, неплохо иметь вероятность делать привязку в обе стороны (two way binding). Для реализации перечисленного хелперу потребуются три входных параметра:

  1. Качество визуального элемента
  2. Событие, уведомляющее об изменениях
  3. Источник данных для привязки

К примеру, для PasswordBox это будут соответственно: качество Password, событие PasswordChanged и источник OtherProperty.

Есть различные методы достичь желаемого. Остановимся на стандартном для таких случаев механизме — поведениях.

Поведение (behavior) — это класс, тот, что добавляет визуальному элементу дополнительную функциональность. Есть два вида поведений: статические и наследуемые от класса Behavior<T>. Изложение их отличий выходит за рамки статьи. Я предпочел 2-й вариант, как владеющий огромными вероятностями.

Пишем код

Добавим ссылку на сборку System.Windows.Interactivity.dll. Это часть SDK редактора Expression Blend и находится в разделе «Растяжения» окна выбора сборок Visual Studio.

Сотворим класс, наследуемый от Behavior<T>:

public class DependecyPropertyBehavior : Behavior<DependencyObject>
{
}

Тип генерика DependencyObject выбран как особенно всеобщий. чай мы пишем многофункциональный класс, подходящий для всякого элемента, а не только PasswordBox.

Алгорифм работы будет короток. Для привязки от свойства к источнику:

  1. Подписываемся на событие об изменении свойства визуального элемента.
  2. В обработчике записываем обновленное значение в источник.

Для обратной привязки:

  1. Через привязку определяем момент метаморфозы значения источника.
  2. Записываем обновленное значение в качество визуального элемента.

Для вышеописанных входных параметров сотворим три свойства:

public string Property { get; set; }
public string UpdateEvent { get; set; }

public static readonly DependencyProperty BindingProperty = DependencyProperty.RegisterAttached(
    "Binding",
    typeof(object),
    typeof(DependecyPropertyBehavior),
    new FrameworkPropertyMetadata { BindsTwoWayByDefault = true }
    );

public object Binding
{
    get { return GetValue(BindingProperty); }
    set { SetValue(BindingProperty, value); }
}
  • Property — имя свойства визуального элемента, не поддерживающее привязку;
  • UpdateEvent — имя события, уведомляющего об изменении значения нашего свойства;
  • Binding - качество зависимостей для привязки к источнику данных. Разрешает использовать приятель механизм привязки.

В данном случае свойства Property и UpdateEvent являются обыкновенными, этого довольно. Качество Binding, наоборот, должно быть свойством зависимостей, чай именно сюда подключается источник данных.

Сейчас, когда у нас есть все входные данные, приступим к их обработке в переопределенном способеOnAttached(). Он вызывается при присоединении поведения к визуальному элементу. К последнему дозволено обращаться через качество класса AssociatedObject. В противовес, при отсоединении вызывается OnDetaching().

Нам необходимы объекты для работы со свойством визуального элемента и событием через рефлексию. Ниже показано их приобретение и подписывание на событие, уведомляющее об изменениях свойства визуального элемента.

Дальше в примерах я опустил разные проверки на null, Дабы не загрязнять внимание. В итоговом варианте класса они присутствуют.

private Delegate _handler;
private EventInfo _eventInfo;
private PropertyInfo _propertyInfo;

protected override void OnAttached()
{
    Type elementType = AssociatedObject.GetType();

    // Получаем качество визуального элемента
    _propertyInfo = elementType.GetProperty(Property, BindingFlags.Instance | BindingFlags.Public);

    // Получаем событие, уведомляющее об изменении визуального элемента
    _eventInfo = elementType.GetEvent(UpdateEvent);

    // Создаем делегат для подписывания на событие
    _handler = CreateDelegateForEvent(_eventInfo, EventFired);

    // Подписываемся
    _eventInfo.AddEventHandler(AssociatedObject, _handler);
}

protected override void OnDetaching()
{
    // Отписываемся
    _eventInfo.RemoveEventHandler(AssociatedObject, _handler);
}

В коде выше имеется метод CreateDelegateForEvent(). Он компилирует объект делегата для указанного события во время выполнения. чай предварительно мы не знаем сигнатуру обработчика события. При компиляции в делегат помещается вызов метода action, которым в нашем случае является EventFired(). В нем мы будем исполнять надобные нам действия для обновления значения источника данных.

private static Delegate CreateDelegateForEvent(EventInfo eventInfo, Action action)
{
    ParameterExpression[] parameters = 
        eventInfo
        .EventHandlerType
        .GetMethod("Invoke")
        .GetParameters()
        .Select(parameter => Expression.Parameter(parameter.ParameterType))
        .ToArray();

    return Expression.Lambda(
        eventInfo.EventHandlerType,
        Expression.Call(Expression.Constant(action), "Invoke", Type.EmptyTypes),
        parameters
        )
        .Compile();
}

Эта операция достаточно ресурсоемка, но выполняется лишь раз, при подключении поведения. Ее дозволено оптимизировать жертвуя гибкостью, предположив, что события могут быть только RoutedEvent. Тогда, взамен драгоценный компиляции, довольно подписаться на событие с указанием обработчика EventFired(), заранее изменив ему сигнатуру на совместимую с RoutedEventHandler. Но оставим тут подлинный вариант. Несвоевременная оптимизация — зло.

Метод EventFired() предельно примитивен, он записывает новое значение в источник данных:

private void EventFired()
{
    Binding = _propertyInfo.GetValue(AssociatedObject, null);
}

Осталась самая малость — менять значение свойства визуального элемента при изменении источника данных. Для этого подойдет переопределяемый метод OnPropertyChanged(), тот, что информирует об изменениях свойств зависимостей класса. Так как при изменении источника данных меняется и качество Binding, то нам довольно отслеживать его новые значения.

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
    if (e.Property.Name != "Binding") return;

    _propertyInfo.SetValue(AssociatedObject, e.NewValue, null);
    base.OnPropertyChanged(e);
}

Все, как бы бы, восхитительно. Мы задаем новое значение свойства визуального элемента и… получаемStackOverflowException.

Задача в том, что при изменении свойства авpoiler_text”>

using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
using Expression = System.Linq.Expressions.Expression;

namespace DependecyPropertyBehaviorNamesapce
{
    public class DependecyPropertyBehavior : Behavior<DependencyObject>
    {
        private Delegate _handler;
        private EventInfo _eventInfo;
        private PropertyInfo _propertyInfo;

        public static readonly DependencyProperty BindingProperty = DependencyProperty.RegisterAttached(
            "Binding",
            typeof(object),
            typeof(DependecyPropertyBehavior),
            new FrameworkPropertyMetadata { BindsTwoWayByDefault = true }
            );

        public object Binding
        {
            get { return GetValue(BindingProperty); }
            set { SetValue(BindingProperty, value); }
        }

        public string Property { get; set; }
        public string UpdateEvent { get; set; }

        protected override void OnAttached()
        {
            Type elementType = AssociatedObject.GetType();

            // Getting property.

            if (Property == null)
            {
                PresentationTraceSources.DependencyPropertySource.TraceData(
                    TraceEventType.Error,
                    1, 
                    "Target property not defined."
                    );
                return;
            }

            _propertyInfo = elementType.GetProperty(Property, BindingFlags.Instance | BindingFlags.Public);

            if (_propertyInfo == null)
            {
                PresentationTraceSources.DependencyPropertySource.TraceData(
                    TraceEventType.Error,
                    2,
                    string.Format("Property \"{0}\" not found.", Property)
                    );
                return;
            }

            // Getting event.

            if (UpdateEvent == null) return;
            _eventInfo = elementType.GetEvent(UpdateEvent);

            if (_eventInfo == null)
            {
                PresentationTraceSources.MarkupSource.TraceData(
                    TraceEventType.Error, 
                    3,
                    string.Format("Event \"{0}\" not found.", UpdateEvent)
                    );
                return;
            }

            _handler = CreateDelegateForEvent(_eventInfo, EventFired);
            _eventInfo.AddEventHandler(AssociatedObject, _handler);
        }

        protected override void OnDetaching()
        {
            if (_eventInfo == null) return;
            if (_handler == null) return;

            _eventInfo.RemoveEventHandler(AssociatedObject, _handler);
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property.Name != "Binding") return;
            if (AssociatedObject == null) return;
            if (_propertyInfo == null) return;

            object oldValue = _propertyInfo.GetValue(AssociatedObject, null);
            if (oldValue.Equals(e.NewValue)) return;

            _propertyInfo.SetValue(AssociatedObject, e.NewValue, null);

            base.OnPropertyChanged(e);
        }

        private static Delegate CreateDelegateForEvent(EventInfo eventInfo, Action action)
        {
            ParameterExpression[] parameters = 
                eventInfo
                .EventHandlerType
                .GetMethod("Invoke")
                .GetParameters()
                .Select(parameter => Expression.Parameter(parameter.ParameterType))
                .ToArray();

            return Expression.Lambda(
                eventInfo.EventHandlerType,
                Expression.Call(Expression.Constant(action), "Invoke", Type.EmptyTypes),
                parameters
                )
                .Compile();
        }

        private void EventFired()
        {
            if (AssociatedObject == null) return;
            if (_propertyInfo == null) return;

            Binding = _propertyInfo.GetValue(AssociatedObject, null);
        }
    }
}

Github
Мой тех.блог

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB