Создание прокси-сервера INotifyPropertyChanged для отправки вызовов в поток пользовательского интерфейса - PullRequest
7 голосов
/ 25 января 2010

Я хотел бы создать динамический прокси-сервер для привязки элементов управления WinForms к объектам, измененным другим (не GUI) потоком. Такой прокси будет перехватывать событие PropertyChanged и отправлять его, используя соответствующий SynchronizationContext.

Таким образом, я мог бы использовать вспомогательный класс для выполнения работы без необходимости каждый раз выполнять синхронизацию вручную (if (control.InvokeRequired) etc.).

Есть ли способ сделать это с помощью LinFu, Castle или подобной библиотеки?

[Изменить]

Источник данных не обязательно является списком. Это может быть любой бизнес-объект, например ::

interface IConnection : INotifyPropertyChanged
{
    ConnectionStatus Status { get; }
}

Я мог бы создать оболочку, которая могла бы выполнять работу, и это выглядело бы примерно так:

public class ConnectionWrapper : IConnection
{
     private readonly SynchronizationContext _ctx;
     private readonly IConnection _actual;
     public ConnectionWrapper(IConnection actual)
     {
         _ctx = SynchronizationContext.Current;
         _actual= actual;
         _actual.PropertyChanged += 
            new PropertyChangedEventHandler(actual_PropertyChanged);
     }

     // we have to do 2 things:
     // 1. wrap each property manually
     // 2. handle the source event and fire it on the GUI thread

     private void PropertyChanged(object sender, PropertyChangedEvArgs e)
     {
         // we will send the same event args to the GUI thread
         _ctx.Send(delegate { this.PropertyChanged(sender, e); }, null);
     }

     public ConnectionStatus Status 
     { get { return _instance.Status; } }

     public event PropertyChangedEventHandler PropertyChanged;
}

(могут быть некоторые ошибки в этом коде, я делаю это)

Я хотел бы иметь для этого динамический прокси (Reflection.Emit), один лайнер, например,

IConnection syncConnection
      = new SyncPropertyChangedProxy<IConnection>(actualConnection);

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

Более общий вопрос: Как перехватить событие при создании динамического прокси? Перехват (переопределение) свойств хорошо объясняется во всех реализациях.

[Edit2]

Причина (я думаю) мне нужен прокси в том, что трассировка стека выглядит так:

at PropertyManager.OnCurrentChanged(System.EventArgs e)
at BindToObject.PropValueChanged(object sender, EventArgs e)
at PropertyDescriptor.OnValueChanged(object component, EventArgs e) 
at ReflectPropertyDescriptor.OnValueChanged(object component, EventArgs e)
at ReflectPropertyDescriptor.OnINotifyPropertyChanged(object component,
     PropertyChangedEventArgs e)    
at MyObject.OnPropertyChanged(string propertyName)

Вы можете видеть, что BindToObject.PropValueChanged не передает экземпляр sender в PropertyManager, и Reflector показывает, что на объект отправителя нигде не ссылаются. Другими словами, когда срабатывает событие PropertyChanged, компонент будет использовать отражение для доступа к свойству исходного (связанного) источника данных .

Если бы я обернул свой объект в класс, содержащий только событие (как предложено Сэм ), такой класс-обертка не будет содержать никаких свойств, к которым можно получить доступ через Reflection.

Ответы [ 2 ]

5 голосов
/ 27 января 2010

Вот класс, который обернет INotifyPropertyChanged, перенаправит событие PropertyChanged через SynchronizationContext.Current и перенаправит свойство.

Это решение должно работать, но со временем его можно было бы улучшить, используя лямбда-выражение вместо имени свойства. Это позволило бы избавиться от отражения, обеспечить типизированный доступ к свойству. Сложность в том, что вам нужно также получить дерево выражений из лямбды, чтобы вытащить имя свойства, чтобы вы могли использовать его в методе OnSourcePropertyChanged. Я видел сообщение о том, как извлекать имя свойства из дерева лямбда-выражений, но не смог найти его сейчас.

Чтобы использовать этот класс, вам нужно изменить привязку следующим образом:

Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value");

А вот SyncBindingWrapper:

using System.ComponentModel;
using System.Reflection;
using System.Threading;

public class SyncBindingWrapper<T> : INotifyPropertyChanged
{
    private readonly INotifyPropertyChanged _source;
    private readonly PropertyInfo _property;

    public event PropertyChangedEventHandler PropertyChanged;

    public T Value
    {
        get
        {
            return (T)_property.GetValue(_source, null);
        }
    }

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
    {
        _source = source;
        _property = source.GetType().GetProperty(propertyName);
        source.PropertyChanged += OnSourcePropertyChanged;
    }

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName != _property.Name)
        {
            return;
        }
        PropertyChangedEventHandler propertyChanged = PropertyChanged;
        if (propertyChanged == null)
        {
            return;
        }

        SynchronizationContext.Current.Send(state => propertyChanged(this, e), null);
    }
}
3 голосов
/ 18 февраля 2013

Я сталкивался с теми же проблемами, и решение Сэмюэля у меня не сработало, поэтому я поместил инициализацию контекста синхронизации в конструктор, и вместо исходного свойства нужно передать имя свойства "Value". Это сработало для меня:

public class SyncBindingWrapper: INotifyPropertyChanged
{
    private readonly INotifyPropertyChanged _source;
    private readonly PropertyInfo _property;

    public event PropertyChangedEventHandler PropertyChanged;

    private readonly SynchronizationContext _context;

    public object Value
    {
        get
        {
            return _property.GetValue(_source, null);
        }
    }

    public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
    {
        _context = SynchronizationContext.Current;
        _source = source;
        _property = source.GetType().GetProperty(propertyName);
        source.PropertyChanged += OnSourcePropertyChanged;
    }

    private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null && e.PropertyName == _property.Name)
        {
            _context.Send(state => propertyChanged(this, new PropertyChangedEventArgs("Value")), null);
        }
    }
}

Использование:

_textBox1.DataBindings.Add("Text", new SyncBindingWrapper(someObject, "SomeProperty"), "Value");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...