【问题标题】:How to get rid of repetitive properties in WPF MVVM viewmodels如何摆脱 WPF MVVM 视图模型中的重复属性
【发布时间】:2013-10-30 17:25:07
【问题描述】:

我正在设置一个带有ViewModel 的 WPF 应用程序,它有很多属性。这些都是非常重复的,我想知道是否有办法摆脱这种情况。这是一个属性的样子,我有大约 8-10 个。

public string Name
{
    get
    {
        return this.name;
    }

    set
    {
        if (this.name != value)
        {
            this.name = value;
            this.RaisePropertyChanged("Name");
        }
    }
}

【问题讨论】:

  • 谢谢,有趣的阅读..
  • 确实......我想复杂性在于确定“等于”的含义。就我自己而言,我想我宁愿使用详细的 setter(以及 VS 代码 sn-p 以减轻重复写出它的痛苦)。
  • 在回答这个问题(我放弃了)时,我遇到了 UpdateControls,一个 NuGet 包:updatecontrols.net/cs/index.shtml 从网页中,“更新控件不需要您实现 INotifyPropertyChanged 或声明一个 DependencyProperty。它将控件直接连接到 CLR 属性。我将自己调查这个包。希望你会发现这很有帮助。另外,我在这里发现了 UpdateControls:blog.excastle.com/2012/12/16/…

标签: c# wpf mvvm prism


【解决方案1】:

我的建议是,如果您的要求很简单,请选择第三方。这是一个已解决的问题,感谢一些聪明的人......

您可以编写代码的最简单的方式是完全删除 INotifyPropertyChanged 实现,并以这样的最小方式编写您的属性:

public string Name { get; set; }

然后将Fody.PropertyChanged 添加到您的项目(位于NuGet)并使用[ImplementPropertyChanged] 属性标记您的类。

Fody 将在编译期间执行一些巧妙的 IL 魔术,以神奇的方式实现接口和所有样板代码 - 这意味着您编写的代码尽可能简单,并且您的最终结果正是您想要的。

请注意,如果您在代码中的其他地方依赖INotifyPropertyChanged 接口(也就是说,如果您在代码或类似代码中手动附加到事件),您可能希望以不同的方式使用 Fody,因为 IDE 不会意识到您已经实现了接口。幸运的是,Fody 也会在其他场景中自动实现(例如:在一个类中实现 INotifyPropertyChanged,而 Fody 也会默认实现 implement event raising in your properties)。

【讨论】:

  • 谢谢 dan,看起来也很有趣。似乎有很多方法可以解决这个问题,仅仅看这样一个简单的事情就可以让我忙几天......你知道在 Visual Studio 之外运行构建时 Fody 是否也可以工作吗?
  • Fody 当然也适用于服务器和命令行构建,如果这就是你的意思的话。 (我相信它在项目文件中注入了一个构建后步骤,允许它注入处理程序,尽管我没有任何代码可以方便地确认这一点。)
【解决方案2】:

提到的线程确实包含答案,但您需要进行一些挖掘。我将展示我在那里找到的两个最佳答案。

第一个解决方案是实现一个ViewModelBase类,将set方法封装成模板方法,并使用lamda表达式检索Property名称,因此重构不会破坏属性名称字符串。

public class ViewModelBase: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

    protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(selectorExpression);
        return true;
    }
}

用法:

class ViewModel : DataBase
{
    private String _prop1;
    public String Prop1
    {
        get { return _prop1; }
        set
        {
            SetField(ref _prop1, value, () => Prop1);
        }
    }
}

第二种解决方案使用字典将属性存储在基类中。这样我们就不需要传入旧值,因为它保存在基类中,并且我们不需要创建成员字段来保存属性的值。我最喜欢这个解决方案:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _propertyValueStorage;

    #region Constructor

    protected ViewModelBase()
    {
        this._propertyValueStorage = new Dictionary<string, object>();
    }

    #endregion

    protected void SetValue<T>(Expression<Func<T>> property, T value)
    {
        var lambdaExpression = property as LambdaExpression;

        if (lambdaExpression == null)
        {
            throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
        }

        var propertyName = this.getPropertyName(lambdaExpression);
        var storedValue = this.getValue<T>(propertyName);

        if (object.Equals(storedValue, value)) return;

        this._propertyValueStorage[propertyName] = value;
        this.OnPropertyChanged(propertyName);
    }

    protected T GetValue<T>(Expression<Func<T>> property)
    {
        var lambdaExpression = property as LambdaExpression;

        if (lambdaExpression == null)
        {
            throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
        }

        var propertyName = this.getPropertyName(lambdaExpression);
        return getValue<T>(propertyName);
    }

    private T getValue<T>(string propertyName)
    {
        object value;
        if (_propertyValueStorage.TryGetValue(propertyName, out value))
        {
            return (T)value;
        }
        return default(T);

    }

    private string getPropertyName(LambdaExpression lambdaExpression)
    {
        MemberExpression memberExpression;

        if (lambdaExpression.Body is UnaryExpression)
        {
            var unaryExpression = lambdaExpression.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambdaExpression.Body as MemberExpression;
        }

        return memberExpression.Member.Name;
    }

    #region < INotifyPropertyChanged > Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion

}

用法如下:

public class ViewModel : ViewModelBase
{
    public String Prop1
    {
        get { return GetValue(() => Prop1); }
        set { SetValue(() => Prop1, value); }
    }
    public bool Bool1
    {
        get { return GetValue(() => Bool1); }
        set { SetValue(() => Bool1, value); }
    }

解决方案 1 基于 https://stackoverflow.com/a/1316566/2259878https://stackoverflow.com/a/1316566/2259878

解决方案 2 基于http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

【讨论】:

  • 值得注意的是,您可以使用 .NET4.5 CallerMemberName 属性而不是从表达式中解析属性名称。
  • 你是对的,但我还在 4.0.但这可能值得升级到 VS2012/13
  • 您可以将 CallMemeberName 与 .net4 项目一起使用,以防您引用 Microsoft.BCL.Portability 并安装 KB2468871
  • 谢谢您,将检查 KB2468871
  • 让每个人都使用它并构建服务器似乎有点复杂。
【解决方案3】:

这取决于需求,如果所有属性都用于相同的目的意味着像name1,name2,name3.....name10,比如列出10个人的名字,然后放入另一个类并绑定一个集合xaml 中 Items-control 的类类型。或者简单地绑定字符串的 ObservableCollection

但是如果每个属性都有自己的用途,那就无法避免了,因为属性只不过是保存不同值的变量。每个属性都有自己的意图,对每个属性的操作在视图模型中会有所不同,具体取决于逻辑

【讨论】:

  • 我正在使用它一个实时应用程序来显示不同区域的状态信息
【解决方案4】:

我的解决方案接近于 uncletall 的,但在使用上有所改变

    private static readonly Properties<MainWindowViewModel> _properties = new Properties<MainWindowViewModel>();

    public static Property TextProperty = _properties.Create(_ => _.Text);
    private string _text;
    public string Text
    {
        get { return _text; }
        set { SetProperty(ref _text, value, TextProperty); }
    }

XAML:

<Label Grid.Row="1" Content="{Model:PropertyBinding  {x:Static Model:MainWindowViewModel.TextProperty}}" Width="200"/>

此示例的好处是在编译时检查更改。 完整样例link

【讨论】:

  • 感谢工作示例项目。但我不太明白你提到的编译时检查的好处,你的解决方案比我的有什么好处吗?但作为一个缺点,我看到了用于创建属性和定义字段的另外两个语句。
  • 我在 xaml 中看到了一个巨大的好处 - 任何属性名称更改都将导致更改 xaml 以及没有运行时错误。是的,需要定义 _props。但无论如何,代码都保持可读性和可理解性。
  • 另外,如果你想创造一个“魔法”,那么你需要实现你的自定义解决方案。这并不容易。我们在生产代码中使用我们的自定义属性通知器,但我无法与您共享信息。您有所有可能的简单实现方式作为支持自动通知的答案。
猜你喜欢
  • 2020-01-17
  • 2011-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-10
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多