简体   繁体   English

是否应路由MVVM事件?

[英]Should MVVM events be routed?

If I have a hierarchy of view model instances, should I route events? 如果我具有视图模型实例的层次结构,是否应该路由事件?

For example, say we have 例如,说我们有

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

and

class B
{
    A _parent

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

Should the B call NotifyPropertyChanged in A . 如若B呼叫NotifyPropertyChangedA

The argument for routing is that it can be awfully convenient. 路由的论点是它可以非常方便。 In particular, if instead of one child, A has a collection of B, becoming informed of any change any child of A becomes very difficult. 尤其是,如果A代替一个孩子,而是拥有B的集合,那么任何A的孩子都会被告知任何更改,这将变得非常困难。 Also, there is a sender first argument, why not use it... the argument against is that the parent event can become crowded. 另外,有一个发送者第一个参数,为什么不使用它呢?反对的理由是,父事件可能变得很拥挤。

Any opinion? 有什么意见吗?

If your front end bindings are actually binding to the child objects like: 如果您的前端绑定实际上是绑定到子对象,例如:

{Binding B.PropertyName}

, then there's not really a need to bubble the event up like that. ,则实际上并不需要像这样冒泡事件。 If your parent ViewModel actually needs to change other properties or do some work on the child when that property changes, then it might be a good idea. 如果您的父级ViewModel实际上需要更改其他属性或在该属性更改时对子级做一些工作,那么这可能是个好主意。

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. 让父母聆听孩子提出的财产变更通知事件,并设置其属性并相应地引发其财产变更事件。

I would invert the event routing. 我会反转事件路由。 You could have class A (parent) attach to PropertyChanged event of its B property, such that anytime class B raises a PropertyChanged event, class A will be notified of a change in B and can then raise its PropertyChanged event. 您可以将类A(父类)附加到其B属性的PropertyChanged事件,这样,无论何时类B引发PropertyChanged事件,类A都会收到有关B更改的通知,然后可以引发其PropertyChanged事件。

You can also utilize a monitoring class that handles raising of property changes to a delegate. 您还可以利用监视类来处理对委托进行的属性更改。 Here is a quick example (not meant for production code). 这是一个简单的示例(不适用于生产代码)。

Assume we have a Person class that exposes a Name property that describes the full name of the person (last, first, and middle name). 假设我们有一个Person类,它公开了描述该人的全名(姓,名和中间名)的Name属性。 We want to raise the PropertyChanged event for the Name property whenever one of the sub-properties of the Name are modified. 每当要修改Name的子属性之一时,我们都希望为Name属性引发PropertyChanged事件。

FullName class: 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 class: 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 class: 人类:

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
}

Note that in the constructor of Person, we register the Name property with the monitor and indicate that delegate method we wish to have executed whenever the PropertyChanged event is raised by the source being monitored for changes. 请注意,在Person的构造函数中,我们向监视器注册Name属性,并指示只要要监视更改的源引发PropertyChanged事件,便希望执行委托方法。 If you have multiple properties you wish to monitor and raise a PropertyChanged event for, it becomes simple to add a single line of code that handles the wiring of the event notification. 如果您希望监视多个属性并引发PropertyChanged事件,则添加单行代码来处理事件通知的连接变得很简单。

This implementation should probably be modified to register and unregister with the monitor each time the Name property is changed within the Name property setter, but I think this gives you a gist of the idea. 每次在Name属性设置器中更改Name属性时,都应该修改此实现以在监视器上注册和注销,但是我认为这可以使您理解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM