MVVM INotifyPropertyChanged具有自动属性名称实现

时间:2013-02-26 11:41:09

标签: .net mvvm inotifypropertychanged

根据我的理解,我们可以在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还有很长的路要走在未来,但我在自己的时间学习。

所以,我的问题来自那些使用它的人,与删除错误选项相比,是否真的更好的方法是提醒用户注意错误(或者你觉得我错过了什么? );问题是,约什·史密斯是公认的,是这个领域的专家,所以如果他提出这种方法,那么通常我会盲目跟随,但在这种情况下,我不禁对其进行测验,并感到有必要了解更多。 / p>

3 个答案:

答案 0 :(得分:2)

您可以通过抽象基类执行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();

答案 1 :(得分:2)

我已经进行了自己的实施调整来管理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线程调度功能   - 对通知的属性进行调试检查   - 多属性通知功能

第二部分是集中式类,它重新组合我的应用程序中使用的所有通知事件args。如果多次使用属性,这主要是避免在整个应用程序中具有多个相同的硬编码字符串。但是代码重构也更容易实现可维护性。

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 :(得分:1)

使用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方法,但似乎它在Portable Class Libraries中不起作用。