C# 如何在MVVM中编写ViewModelBase

C# 如何在MVVM中编写ViewModelBase,c#,wpf,mvvm,viewmodel,C#,Wpf,Mvvm,Viewmodel,我是WPF编程环境的新手。我正在尝试使用MVVM设计模式编写一个程序 我做了一些研究,读了一些与之相关的文章,很多时候我遇到了这个叫做 ViewModelBase 我知道那是什么。。但是,我可以明确地我应该从哪里开始写我自己的ViewModelBase吗?喜欢真正理解正在发生的事情,而不会变得太复杂。谢谢:)您有一些nuget包来实现MVVM MVVM灯 MVVM交叉 棱镜 对我来说,对于初学者来说,MVVM light更容易,因为它提供了一些代码示例 因此,最好安装这个nuget软件包,查看生

我是WPF编程环境的新手。我正在尝试使用MVVM设计模式编写一个程序

我做了一些研究,读了一些与之相关的文章,很多时候我遇到了这个叫做

ViewModelBase


我知道那是什么。。但是,我可以明确地我应该从哪里开始写我自己的ViewModelBase吗?喜欢真正理解正在发生的事情,而不会变得太复杂。谢谢:)

您有一些nuget包来实现MVVM

  • MVVM灯
  • MVVM交叉
  • 棱镜
  • 对我来说,对于初学者来说,MVVM light更容易,因为它提供了一些代码示例


    因此,最好安装这个nuget软件包,查看生成的代码,如果需要,请返回给我们以获取更多解释。

    如果您不知道内部发生了什么,那么使用MVVM框架是毫无价值的

    因此,让我们一步一步地构建您自己的ViewModelBase类

  • ViewModelBase是所有viewmodels的通用类。让我们将所有常用逻辑移到这个类中

  • 您的ViewModels应该实现INotifyPropertyChanged(您知道为什么吗?)

    [CallerMemberName]
    属性不是必需的,但它允许您编写:
    OnPropertyChanged()而不是
    OnPropertyChanged(“SomeProperty”),因此您将避免在代码中使用字符串常量。例如:

    public string FirstName
    {
        set
        {
            _firtName = value;
            OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
        }
        get{ return _firstName;}
    }
    
    请注意,
    OnPropertyChanged(()=>SomeProperty)
    不再推荐,因为我们在C#6中有
    nameof
    操作符

  • 通常的做法是实现调用PropertyChanged的属性,如下所示:

    public string FirstName
    {
        get { return _firstName; }
        set { SetProperty(ref _firstName, value); }
    }
    
    让我们在viewmodelbase中定义SetProperty:

    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }
    
    如果我有一个应用程序,它使用基于页面的导航,我还为页面视图模型指定基类

    public abstract PageViewModelBase : ViewModelBase
    {
        //for example all my pages has title:
        public string Title {get; private set;}
    }
    
    我可以再上一节对话课:

    public abstract DialogViewModelBase : ViewModelBase
    {
        private bool? _dialogResult;
    
        public event EventHandler Closing;
    
        public string Title {get; private set;}
        public ObservableCollection<DialogButton> DialogButtons { get; }
    
        public bool? DialogResult
        {
            get { return _dialogResult; }
            set { SetProperty(ref _dialogResult, value); }
        }
    
        public void Close()
        {
            Closing?.Invoke(this, EventArgs.Empty);
        }
    }
    
    公共抽象对话框ViewModelBase:ViewModelBase { 私有布尔?\u对话框结果; 公共事件处理程序关闭; 公共字符串标题{get;private set;} 公共ObservableCollection对话框按钮{get;} 公共布尔?对话结果 { 获取{return\u dialogResult;} set{SetProperty(ref _dialogResult,value);} } 公众假期结束() { 关闭?.Invoke(此为EventArgs.Empty); } }
    在大多数MVVM框架中,基本ViewModel类实际上只包含很少的代码—通常只是INotifyPropertyChanged和一些帮助函数的实现


    请看一下MVVM Light和类的源代码。ObserveObject主要是INotifyPropertyChanged实现——使用lambda表达式而不是属性名的“魔术字符串”。ViewModelBase扩展了ObserveObject,主要是一种实用方法,用于确定您是否在Visual Studio designer中运行

    我喜欢它为您的视图模型提供了一种非常干净的样式。查看各种“之前”和“之后”比较。当然,没有什么是强制性的——如果您不喜欢BaseViewModel提供的功能,那么就不要使用它。或者修改它,因为您有源代码。特别要注意的是,有三种不同的方法可以通过更改通知实现属性-选择您理解/熟悉的复杂程度。

    以下类可以用作WPF项目中的ViewModelBase:

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Multicast event for property change notifications.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    
        /// <summary>
        /// Checks if a property already matches the desired value.  Sets the property and
        /// notifies listeners only when necessary.
        /// </summary>
        /// <typeparam name="T">Type of the property.</typeparam>
        /// <param name="storage">Reference to a property with both getter and setter.</param>
        /// <param name="value">Desired value for the property.</param>
        /// <param name="propertyName">Name of the property used to notify listeners.This
        /// value is optional and can be provided automatically when invoked from compilers that
        /// support CallerMemberName.</param>
        /// <returns>True if the value was changed, false if the existing value matched the
        /// desired value.</returns>
        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (object.Equals(storage, value)) return false;
            storage = value;
            // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
            this.OnPropertyChanged(propertyName);
            return true;
        }
    
        /// <summary>
        /// Notifies listeners that a property value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property used to notify listeners.  This
        /// value is optional and can be provided automatically when invoked from compilers
        /// that support <see cref="CallerMemberNameAttribute"/>.</param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    为便于书写,可使用以下内容:

    <?xml version="1.0" encoding="utf-8"?>  
    <CodeSnippets  
        xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">  
        <CodeSnippet Format="1.0.0">  
            <Header>  
                <Title>OnPropertyChanged</Title>  
            </Header>      
          <Snippet>
              <SnippetTypes>
                <SnippetType>SurroundsWith</SnippetType>
                <SnippetType>Expansion</SnippetType>
              </SnippetTypes>
              <Declarations>
                <Literal>
                  <ID>TYPE</ID>
                  <ToolTip>Property type</ToolTip>
                  <Default>int</Default>
                </Literal>
                <Literal>
                  <ID>NAME1</ID>
                  <ToolTip>Property name</ToolTip>
                  <Default>MyProperty</Default>
                </Literal>
              </Declarations>
                <Code Language="CSharp">  
                    <![CDATA[private $TYPE$ _$NAME1$;
    public $TYPE$ $NAME1$
    {
        get => _$NAME1$; 
        set => SetProperty(ref _$NAME1$, value);    
    }]]>  
                </Code>  
            </Snippet>  
        </CodeSnippet>  
    </CodeSnippets>  
    
    
    不动产变更
    周边地区
    膨胀
    类型
    属性类型
    int
    名称1
    属性名
    我的财产
    
    _$NAME1$;
    set=>SetProperty(ref$NAME1$,value);
    }]]>  
    
    
    今天,为了重温这个答案,我想在为VisualStudio编写MVVM代码时提供额外的生产力改进

    Visual Studio中的Intellisense可以自动创建
    SetProperty
    样板方法。为此,我在窗口的XAML中设置了ViewModel(见下文)。然后,每当我引用
    {Binding Path=NewProperty}
    ,右键单击并选择
    快速操作和重构…
    (或通过
    Ctrl.
    )。如果未创建
    SetProperty
    方法,将在ViewModel类中自动为您创建该方法。此外,它将生成绑定所需的属性和字段

    
    ...
    
    然而,这种方法有缺点

  • INotifyPropertyChanged
    未实现,并且未实现
    OnPropertyChanged
    方法(如果需要)
  • 这需要在每个ViewModel中完成
  • 这是特定于VisualStudio的
  • 好处:

  • 一旦在项目中定义了
    SetProperty
    方法,使用
    快速操作和重构…
    选项将只为您生成必要的属性和字段。如果从
    ViewModelBase
    继承,也可以这样做

  • 下面是Visual Studio生成的
    SetProperty
    方法

    protected virtual bool SetProperty(ref T storage,T value,[CallerMemberName]string propertyName=null)
    {
    if(object.Equals(storage,value))返回false;
    储存=价值;
    //DebugFormat(“{0}.{1}={2}”,this.GetType().Name,propertyName,storage);
    此.OnPropertyChanged(propertyName);
    返回true;
    }
    
    在大多数情况下,它只不过是通知更改的
    RaisePropertyChange
    方法的一个基本实现,但这在许多初学者和纯粹主义者(没有MVV)中很容易找到
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Multicast event for property change notifications.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    
        /// <summary>
        /// Checks if a property already matches the desired value.  Sets the property and
        /// notifies listeners only when necessary.
        /// </summary>
        /// <typeparam name="T">Type of the property.</typeparam>
        /// <param name="storage">Reference to a property with both getter and setter.</param>
        /// <param name="value">Desired value for the property.</param>
        /// <param name="propertyName">Name of the property used to notify listeners.This
        /// value is optional and can be provided automatically when invoked from compilers that
        /// support CallerMemberName.</param>
        /// <returns>True if the value was changed, false if the existing value matched the
        /// desired value.</returns>
        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (object.Equals(storage, value)) return false;
            storage = value;
            // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
            this.OnPropertyChanged(propertyName);
            return true;
        }
    
        /// <summary>
        /// Notifies listeners that a property value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property used to notify listeners.  This
        /// value is optional and can be provided automatically when invoked from compilers
        /// that support <see cref="CallerMemberNameAttribute"/>.</param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    public class MyViewModel : ViewModelBase
    {
        private int myProperty;
        public int MyProperty
        {
            get { return myProperty; }
            set { SetProperty(ref myProperty, value);
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>  
    <CodeSnippets  
        xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">  
        <CodeSnippet Format="1.0.0">  
            <Header>  
                <Title>OnPropertyChanged</Title>  
            </Header>      
          <Snippet>
              <SnippetTypes>
                <SnippetType>SurroundsWith</SnippetType>
                <SnippetType>Expansion</SnippetType>
              </SnippetTypes>
              <Declarations>
                <Literal>
                  <ID>TYPE</ID>
                  <ToolTip>Property type</ToolTip>
                  <Default>int</Default>
                </Literal>
                <Literal>
                  <ID>NAME1</ID>
                  <ToolTip>Property name</ToolTip>
                  <Default>MyProperty</Default>
                </Literal>
              </Declarations>
                <Code Language="CSharp">  
                    <![CDATA[private $TYPE$ _$NAME1$;
    public $TYPE$ $NAME1$
    {
        get => _$NAME1$; 
        set => SetProperty(ref _$NAME1$, value);    
    }]]>  
                </Code>  
            </Snippet>  
        </CodeSnippet>  
    </CodeSnippets>