具有自动属性名称实现的 MVVM INotifyPropertyChanged
Posted
技术标签:
【中文标题】具有自动属性名称实现的 MVVM INotifyPropertyChanged【英文标题】:MVVM INotifyPropertyChanged with Automatic Property Name implementation 【发布时间】:2013-02-11 20:25:55 【问题描述】:据我了解,我们可以在 MVVM 风格的应用程序中使用 INofityProperty,代码类似于以下
object _SelectedPerson;
public object SelectedPerson
get
return _SelectedPerson;
set
if (_SelectedPerson != value)
_SelectedPerson = value;
RaisePropertyChanged("SelectedPerson");
现在,我看到Josh Smith's excellent example 他实现了额外的代码来捕获如果开发人员键入无法识别的属性名称(例如拼写错误)会发生什么!
如果你讨厌这个,请告诉我,但是有一种方法可以从堆栈跟踪中获取方法名称。所以,我们可以改为实现类似
object _SelectedPerson;
public object SelectedPerson
get
return _SelectedPerson;
set
if (_SelectedPerson != value)
_SelectedPerson = value;
RaisePropertyChanged(Current.Method);
static class Current
public static string Method()
StackTrace st = new StackTrace();
return (st.GetFrame(1).GetMethod().Name.Split('_')[1]);
我只能假设这将始终有效,因为 RaisePropertyChanged 事件总是发生在 Setter 中(如果我错了,请纠正我)。
现在请注意,我无法真正尝试这个,因为在工作中(我可以从事更大的项目)我仍在使用 .NET 2.0,因此 WPF/MVVM 还很遥远在未来,但我在自己的时间学习。
所以,我的问题是来自那些使用过它的人,与删除错误选项相比,有一种方法来提醒用户错误真的更好吗(或者你觉得我错过了什么? );问题是,乔什·史密斯是公认的,是该领域的专家,所以如果他建议这种方法,那么通常我会盲目地遵循,但在这种情况下,我不禁要问它,觉得有必要了解更多。
【问题讨论】:
【参考方案1】:我已经对管理 propertychanged 通知进行了自己的实现调整。
第一部分是经典的 NotifierBase 类:
/// <summary>
/// Base class for all notifying objects (model adapters, view models, etc.)
/// </summary>
public abstract class NotifierBase : INotifyPropertyChanged
/// <summary>
/// Private reference to UI thread
/// </summary>
private readonly System.Windows.Threading.Dispatcher _uiThread;
/// <summary>
/// Default Constructor
/// </summary>
protected NotifierBase()
_uiThread = Application.Current != null ? Application.Current.Dispatcher : System.Windows.Threading.Dispatcher.CurrentDispatcher;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
/// <summary>
/// Explicit raise of a property changed notification
/// </summary>
/// <param name="e"> </param>
protected void Notify(PropertyChangedEventArgs e)
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
//Debug method used to verify that the property we are about to raise is really a member of the current class instance
CheckProperty(e.PropertyName);
//raises the notification
ToUiThread(() => handler(this, e));
protected void Notify(params PropertyChangedEventArgs[] e)
foreach (var pcea in e)
Notify(pcea);
/// <summary>
/// Dispatch an action to the ui thread
/// </summary>
/// <param name="action"> Action to dispatch </param>
protected void ToUiThread(Action action)
if (_uiThread.CheckAccess()) //if we are already in the UI thread, invoke action
action();
else
//otherwise dispatch in the ui thread
_uiThread.Invoke(action);
/// <summary>
/// Check if the raised property is a valid property for the current instance type
/// </summary>
/// <param name="propertyName"> Name of the raised property </param>
[DebuggerStepThrough]
private void CheckProperty(string propertyName)
Type type = GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (properties.Any(pi => pi.Name == propertyName)) return;
throw new InvalidOperationException(
string.Format("Trying to raise notification on property \"0\" which does not exists on type \"1\"",
propertyName, type.Name));
基本上,这个类提供: - 一个简单的 UI 线程调度功能 - 对通知的属性进行调试检查 - 多属性通知功能
第二部分是集中类,它重新组合了我的应用程序中使用的所有通知事件参数。如果一个属性被多次使用,这主要是避免在整个应用程序中使用多个相同的硬编码字符串。但总体而言,代码重构和可维护性也更容易。
public class Npcea
public static readonly PropertyChangedEventArgs BarCode = new PropertyChangedEventArgs("BarCode");
public static readonly PropertyChangedEventArgs Cap = new PropertyChangedEventArgs("Cap");
public static readonly PropertyChangedEventArgs Code = new PropertyChangedEventArgs("Code");
public static readonly PropertyChangedEventArgs Status = new PropertyChangedEventArgs("Status");
public static readonly PropertyChangedEventArgs Comments = new PropertyChangedEventArgs("Comments");
所以基本上,在您的视图模型(或适配器或其他)中,您只需通知类似的属性
public ResourceStatus Status
get return _status;
set
_status = value;
Notify(Npcea.Status,Npcea.Comments);
希望这会有所帮助
--布鲁诺
【讨论】:
【参考方案2】:使用 StackTrace 的问题是,它没有在发布版本中正确填充。为了克服这个问题,有几种方法可以修复它并使开发人员更容易引发 PropertyChanged 事件。
查看这个问题:Implementing INotifyPropertyChanged - does a better way exist? 并选择适合您的解决方案 :)
我个人更喜欢以下:
// Helper method
public static PropertyChangedEventArgs CreateArguments<TOwner>(Expression<Func<TOwner, object>> Expression)
// determine the Name of the property using the Expression
// Within the view-model implementations:
private static readonly PropertyChangedEventArgs TitleProperty = CreateArguments<MyViewModel>(m => m.Title);
private string title;
public string Title
get return this.title;
set
if (!string.Equals(this.title, value)
this.title = value;
this.OnPropertyChanged(TitleProperty);
通过使用静态成员预生成 PropertyChangedEventArgs,检查表达式树引入的开销是有限的。此解决方案是重构安全的,因此您没有任何魔术字符串。
我也喜欢使用 CallerMemberNameAttribute 的 .NET 4.5 方法,但它似乎不适用于可移植类库。
【讨论】:
这很好,但我没有收到您关于“未在发布版本中正确填充”的评论。你有什么我可以读到的(甚至是我可以自己谷歌的短语吗?)+1 我的意思是你不能信任发布版本中的 StackTrace,因为代码可能会被优化并且方法可能是内联的。如果是属性,你可能会很好,但我一般会避免使用 StackTrace。关于这个主题有几个 SO 讨论,例如:***.com/questions/7585010/…【参考方案3】:您可以通过抽象基类执行 INotifyPropertyChanged。这可能如下所示:
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Event, fired when the Property has changed
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyExpression">() => this.Param</param>
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
var propertyName = ExtractPropertyName(propertyExpression);
OnPropertyChanged(propertyName);
protected void OnPropertyChanged(string propertyName)
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
/// <summary>
/// Extracts the propertyname out of the Expression given
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyExpression"></param>
/// <returns></returns>
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
var memberExpression = propertyExpression.Body as MemberExpression;
return memberExpression == null ? null : memberExpression.Member.Name;
在 .Net 4.5 中,您可以创建如下类:
public class ViewModelBase : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
你只需要打电话
OnPropertyChanged();
【讨论】:
以上是关于具有自动属性名称实现的 MVVM INotifyPropertyChanged的主要内容,如果未能解决你的问题,请参考以下文章