如果我有视图模型实例的层次结构,我应该路由事件吗?
例如,假设我们有
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的任何一个孩子变得非常困难时,它会被告知任何变化。另外,有一个发件人的第一个参数,为什么不使用它... 反对的论点是父事件可能变得拥挤。
有什么意见吗?
答案 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属性时,可能应该修改此实现以注册和取消注册监视器,但我认为这给了你一个想法的要点。