Следует ли маршрутизировать события MVVM? - PullRequest
1 голос
/ 16 декабря 2010

Если у меня есть иерархия экземпляров модели представления, я должен маршрутизировать события?

Например, скажем, у нас есть

class A: INotifyPropertyChanged
{
    public B Child ...
}

и

class B
{
    A _parent

    void OnPropertyChanged (string propertyName)
    {
        if (PropertyChanged != null) PropertyChanged (this, propertyName);
        ///Should I call _parent.OnPropertyChanged (this, propertyName);?////
     }
}

ДолженB вызов NotifyPropertyChanged в A.

Аргументом для маршрутизации является то, что это может быть очень удобно.В частности, если вместо одного дочернего элемента у A есть набор B, получение информации о любых изменениях любого дочернего элемента A становится очень трудным.Кроме того, есть первый аргумент отправителя, почему бы не использовать его ... аргумент против состоит в том, что родительское событие может стать переполненным.

Любое мнение?

Ответы [ 3 ]

2 голосов
/ 16 декабря 2010

Если ваши внешние интерфейсы на самом деле привязаны к дочерним объектам, например:

{Binding B.PropertyName}

, тогда нет нужды раздувать событие таким образом. Если ваш родительский ViewModel действительно должен изменить другие свойства или выполнить какую-либо работу с дочерним элементом при изменении этого свойства, то это может быть хорошей идеей.

1 голос
/ 16 декабря 2010

Если у вас есть дочерний объект, который отправляет уведомление об изменении свойства для своего родителя, вы тесно связываете его с родителем и вовлекаете его в детали реализации родительского объекта.Подумайте: теперь, когда вы реализуете новое свойство в родительском объекте, которое каким-то образом зависит от состояния дочернего элемента, вы должны модифицировать дочерний класс для его поддержки.(или, как мне нравится думать, правильный путь) - сделать объекты неосведомленными о внутренних деталях друг друга.Пусть родитель прослушивает события уведомления об изменении свойства, которые генерируют дочерние элементы, и должен установить свои свойства и соответственно инициировать события изменения свойства.

0 голосов
/ 16 декабря 2010

Я бы инвертировал маршрутизацию событий. Класс A (родительский) может быть присоединен к событию PropertyChanged его свойства B, так что в любое время, когда класс B вызывает событие PropertyChanged, класс A будет уведомлен об изменении в B и затем может вызвать событие PropertyChanged.

Вы также можете использовать класс мониторинга, который обрабатывает изменение свойств делегата. Вот быстрый пример (не предназначен для производственного кода).

Предположим, у нас есть класс Person, который предоставляет свойство Name, которое описывает полное имя человека (фамилия, имя и отчество). Мы хотим вызвать событие PropertyChanged для свойства Name каждый раз, когда изменяется одно из под-свойств Name.

Класс FullName:

public class FullName : INotifyPropertyChanged, IEquatable<FullName>
{
    //=======================================================================================================
    //  Constructors
    //=======================================================================================================
    #region FullName()
    public FullName()
    {

    }
    #endregion

    //=======================================================================================================
    //  Public Properties
    //=======================================================================================================
    #region FirstName
    public string FirstName
    {
        get
        {
            return _firstName;
        }

        set
        {
            if (!String.Equals(_firstName, value, StringComparison.Ordinal))
            {
                _firstName = !String.IsNullOrEmpty(value) ? value.Trim() : String.Empty;
                this.OnPropertyChanged("FirstName");
            }
        }
    }
    private string _firstName = String.Empty;
    #endregion

    #region LastName
    public string LastName
    {
        get
        {
            return _lastName;
        }

        set
        {
            if (!String.Equals(_lastName, value, StringComparison.Ordinal))
            {
                _lastName = !String.IsNullOrEmpty(value) ? value.Trim() : String.Empty;
                this.OnPropertyChanged("LastName");
            }
        }
    }
    private string _lastName = String.Empty;
    #endregion

    #region MiddleName
    public string MiddleName
    {
        get
        {
            return _middleName;
        }

        set
        {
            if (!String.Equals(_middleName, value, StringComparison.Ordinal))
            {
                _middleName = !String.IsNullOrEmpty(value) ? value.Trim() : String.Empty;
                this.OnPropertyChanged("MiddleName");
            }
        }
    }
    private string _middleName = String.Empty;
    #endregion

    //=======================================================================================================
    //  Public Methods
    //=======================================================================================================
    #region Equals(FullName first, FullName second)
    /// <summary>
    /// Determines whether two specified <see cref="FullName"/> objects have the same value.
    /// </summary>
    /// <param name="first">The first role to compare, or <see langword="null"/>.</param>
    /// <param name="second">The second role to compare, or <see langword="null"/>.</param>
    /// <returns>
    /// <see langword="true"/> if the value of <paramref name="first"/> object is the same as the value of <paramref name="second"/> object; otherwise, <see langword="false"/>.
    /// </returns>
    public static bool Equals(FullName first, FullName second)
    {
        if (first == null && second != null)
        {
            return false;
        }
        else if (first != null && second == null)
        {
            return false;
        }
        else if (first == null && second == null)
        {
            return true;
        }
        else
        {
            return first.Equals(second);
        }
    }
    #endregion

    #region ToString()
    /// <summary>
    /// Returns a <see cref="String"/> that represents the current <see cref="FullName"/>.
    /// </summary>
    /// <returns>
    /// A <see cref="String"/> that represents the current <see cref="FullName"/>.
    /// </returns>
    public override string ToString()
    {
        return String.Format(null, "{0}, {1} {2}", this.LastName, this.FirstName, this.MiddleName).Trim();
    }
    #endregion

    //=======================================================================================================
    //  IEquatable<FullName> Implementation
    //=======================================================================================================
    #region Equals(FullName other)
    /// <summary>
    /// Indicates whether the current object is equal to another object of the same type.
    /// </summary>
    /// <param name="other">An object to compare with this object.</param>
    /// <returns><see langword="true"/> if the current object is equal to the other parameter; otherwise, <see langword="false"/>.</returns>
    public bool Equals(FullName other)
    {
        if (other == null)
        {
            return false;
        }

        if (!String.Equals(this.FirstName, other.FirstName, StringComparison.Ordinal))
        {
            return false;
        }
        else if (!String.Equals(this.LastName, other.LastName, StringComparison.Ordinal))
        {
            return false;
        }
        else if (!String.Equals(this.MiddleName, other.MiddleName, StringComparison.Ordinal))
        {
            return false;
        }

        return true;
    }
    #endregion

    #region Equals(object obj)
    /// <summary>
    /// Determines whether the specified <see cref="Object"/> is equal to the current <see cref="Object"/>.
    /// </summary>
    /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="Object"/>.</param>
    /// <returns>
    /// <see langword="true"/> if the specified <see cref="Object"/> is equal to the current <see cref="Object"/>; otherwise, <see langword="false"/>.
    /// </returns>
    public override bool Equals(object obj)
    {
        return this.Equals(obj as FullName);
    }
    #endregion

    #region GetHashCode()
    /// <summary>
    /// Returns the hash code for this instance.
    /// </summary>
    /// <returns>A 32-bit signed integer hash code.</returns>
    /// <a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"/>
    public override int GetHashCode()
    {
        int firstNameHashCode   = this.FirstName.GetHashCode();
        int lastNameHashCode    = this.LastName.GetHashCode();
        int middleNameHashCode  = this.MiddleName.GetHashCode();

        /*
            * The 23 and 37 are arbitrary numbers which are co-prime.
            * 
            * The benefit of the below over the XOR (^) method is that if you have a type 
            * which has two values which are frequently the same, XORing those values 
            * will always give the same result (0) whereas the above will 
            * differentiate between them unless you're very unlucky.
        */
        int hashCode    = 23;
        hashCode        = hashCode * 37 + firstNameHashCode;
        hashCode        = hashCode * 37 + lastNameHashCode;
        hashCode        = hashCode * 37 + middleNameHashCode;

        return hashCode;
    }
    #endregion

    //=======================================================================================================
    //  INotifyPropertyChanged Implementation
    //=======================================================================================================
    #region PropertyChanged
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    /// <remarks>
    /// The <see cref="PropertyChanged"/> event can indicate all properties on the object have changed 
    /// by using either a <b>null</b> reference (Nothing in Visual Basic) or <see cref="String.Empty"/> 
    /// as the property name in the <see cref="PropertyChangedEventArgs"/>.
    /// </remarks>
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    #region OnPropertyChanged(string propertyName)
    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="propertyName">The name of the property that changed.</param>
    protected void OnPropertyChanged(string propertyName)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region OnPropertyChanged(PropertyChangedEventArgs e)
    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }
    #endregion
}

Класс PropertyChangeMonitor:

public class PropertyChangeMonitor
{
    //=======================================================================================================
    //  Constructors
    //=======================================================================================================
    #region PropertyChangeMonitor()
    public PropertyChangeMonitor()
    {

    }
    #endregion

    //=======================================================================================================
    //  Protected Properties
    //=======================================================================================================
    #region Sources
    protected ConcurrentDictionary<INotifyPropertyChanged, Action<string>> Sources
    {
        get
        {
            return _sources;
        }
    }
    private ConcurrentDictionary<INotifyPropertyChanged, Action<string>> _sources = new ConcurrentDictionary<INotifyPropertyChanged,Action<string>>();
    #endregion

    //=======================================================================================================
    //  Public Methods
    //=======================================================================================================
    #region Register(INotifyPropertyChanged source, Action<string> target)
    public void Register(INotifyPropertyChanged source, Action<string> target)
    {
        if(source == null || target == null)
        {
            return;
        }

        if(!this.Sources.ContainsKey(source))
        {
            if (this.Sources.TryAdd(source, target))
            {
                source.PropertyChanged += (o, e) =>
                {
                    target.Invoke(e.PropertyName);
                };
            }
        }
    }
    #endregion

    #region Unregister(INotifyPropertyChanged source, Action<string> target)
    public void Unregister(INotifyPropertyChanged source, Action<string> target)
    {
        if (source == null || target == null)
        {
            return;
        }

        if (this.Sources.ContainsKey(source))
        {
            if (this.Sources.TryRemove(source, out target))
            {
                source.PropertyChanged -= (o, e) =>
                {
                    target.Invoke(e.PropertyName);
                };
            }
        }
    }
    #endregion
}

Класс человека:

public class Person : INotifyPropertyChanged
{
    //=======================================================================================================
    //  Constructors
    //=======================================================================================================
    #region Person()
    public Person()
    {
        this.ChangeMonitor.Register(this.Name, OnPropertyChanged);
    }
    #endregion

    //=======================================================================================================
    //  Protected Properties
    //=======================================================================================================
    #region ChangeMonitor
    protected PropertyChangeMonitor ChangeMonitor
    {
        get
        {
            return _monitor;
        }
    }
    private PropertyChangeMonitor _monitor = new PropertyChangeMonitor();
    #endregion

    //=======================================================================================================
    //  Public Properties
    //=======================================================================================================
    #region Name
    public FullName Name
    {
        get
        {
            return _personName;
        }

        set
        {
            if (!FullName.Equals(_personName, value))
            {
                _personName = value;
                this.OnPropertyChanged("Name");
            }
        }
    }
    private FullName _personName = new FullName();
    #endregion

    //=======================================================================================================
    //  INotifyPropertyChanged Implementation
    //=======================================================================================================
    #region PropertyChanged
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    /// <remarks>
    /// The <see cref="PropertyChanged"/> event can indicate all properties on the object have changed 
    /// by using either a <b>null</b> reference (Nothing in Visual Basic) or <see cref="String.Empty"/> 
    /// as the property name in the <see cref="PropertyChangedEventArgs"/>.
    /// </remarks>
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    #region OnPropertyChanged(string propertyName)
    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="propertyName">The name of the property that changed.</param>
    protected void OnPropertyChanged(string propertyName)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region OnPropertyChanged(PropertyChangedEventArgs e)
    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> event.
    /// </summary>
    /// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }
    #endregion
}

Обратите внимание, что в конструкторе Person мы регистрируем свойство Name в мониторе и указываем, что метод делегата, который мы хотим выполнить, всякий раз, когда событие PropertyChanged вызывается источником, отслеживаемым на предмет изменений. Если у вас есть несколько свойств, которые вы хотите отслеживать и вызывать событие PropertyChanged, становится просто добавить одну строку кода, которая обрабатывает связывание уведомления о событии.

Эту реализацию, вероятно, следует изменить, чтобы регистрировать и отменять регистрацию на мониторе каждый раз, когда свойство Name изменяется в установщике свойства Name, но я думаю, что это дает вам суть идеи.

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