Должны быть направлены события MVVM?

Обновить

February 2019

Просмотры

714 раз

1

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

Например, говорят, что мы имеем

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.

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

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

3 ответы

1

If you have the child object doing property-change notification for its parent, you're tightly coupling the child to the parent, and engaging the child in the implementation details of the parent. Consider: now whenever you implement a new property in the parent that is in some way dependent on the state of a child, you have to modify the child class to support it.

The loosely-coupled way to do this (or, as I like to think of it, the right way) is to make the objects ignorant of each others' internal details. Have the parent listen to property-change notification events that the children raise, and have it set its properties and raise its property-change events accordingly.

0

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

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

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

ПолноеИмя класс:

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
}

Класс Person:

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, мы регистрируем Имя свойства с монитором и указать, что метод делегата мы хотим быть выполнен, когда событие PropertyChanged поднимаются от источника ведется наблюдение за изменения. Если у вас есть несколько свойств, которые вы хотите контролировать и повышать событие PropertyChanged для, становится просто добавить одну строку кода, который обрабатывает проводку уведомления о событии.

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

2

Если передний конец привязки фактически привязка к ребенку таких объектов, как:

{Binding B.PropertyName}

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