MVVM事件应该被路由吗?

时间:2010-12-16 18:29:56

标签: c# .net wpf events mvvm

如果我有视图模型实例的层次结构,我应该路由事件吗?

例如,假设我们有

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来电{{1}}。

路由的论点是它非常方便。特别是,如果不是一个孩子,A有一个B的集合,当A的任何一个孩子变得非常困难时,它会被告知任何变化。另外,有一个发件人的第一个参数,为什么不使用它... 反对的论点是父事件可能变得拥挤。

有什么意见吗?

3 个答案:

答案 0 :(得分:2)

如果您的前端绑定实际上绑定到子对象,如:

{Binding B.PropertyName}

,那么就没有必要像这样鼓吹事件。如果您的父ViewModel实际上需要更改其他属性或在该属性更改时对子进行某些操作,那么这可能是个好主意。

答案 1 :(得分:1)

如果您的子对象为其父级执行属性更改通知,则您将子级紧密耦合到父级,并使子级参与父级的实现详细信息。考虑一下:现在每当你在父类中实现一个新的属性时,你必须修改子类以支持它。

松散耦合的方式(或者,正如我想的那样,正确的方法)是让对象无视彼此的内部细节。让父母听取孩子们提出的属性更改通知事件,并让它设置其属性并相应地提升其属性更改事件。

答案 2 :(得分:0)

我会反转事件路由。您可以将A类(父级)附加到其B属性的PropertyChanged事件,这样,无论何时B类引发PropertyChanged事件,A类都会收到B中的更改通知,然后可以提升其PropertyChanged事件。

您还可以使用监视类来处理向委托提升属性更改的问题。这是一个快速示例(不适用于生产代码)。

假设我们有一个Person类,它公开了一个Name属性,该属性描述了人的全名(姓氏,名字和中间名)。我们想在修改Name的一个子属性时为Name属性引发PropertyChanged事件。

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属性setter中更改Name属性时,可能应该修改此实现以注册和取消注册监视器,但我认为这给了你一个想法的要点。