如何在MVVM中编写ViewModelBase

时间:2016-03-22 08:53:27

标签: c# wpf mvvm viewmodel

我是WPF编程环境中的新手。我试图用MVVM设计模式编写程序。

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

的文章。
  

ViewModelBase

我知道它是什么..但是我可以特别知道我应该从哪里开始,以便能够写出我自己的ViewModelBase?就像......真正理解发生的事情而不会太复杂。谢谢:))

7 个答案:

答案 0 :(得分:60)

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

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

  1. ViewModelBase是所有视图模型的通用类。让我们将所有常见逻辑移到这个类。

  2. 您的ViewModel应该实现INotifyPropertyChanged(您明白为什么吗?)

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    [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运算符。

  3. 通常的做法是实现调用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;
    }
    

    当属性值更改并返回true时,它只会触发PropertyChanged事件。当值未更改并且返回false时,它不会触发事件。基本思想是,SetProperty方法是虚拟的,您可以在更具体的类中扩展它,例如触发验证,或者通过调用PropertyChanging事件。

  4. 这很漂亮。这是您在此阶段应包含的所有ViewModelBase。其余的取决于你的项目。例如,您的应用程序使用页面基本导航,并且您已编写自己的NavigationService以处理来自ViewModel的导航。因此,您可以将NavigationSerivce属性添加到ViewModelBase类,因此,如果需要,您可以从所有视图模型中访问它。

    为了获得更多的可重用性并保持SRP,我有一个名为 BindableBase 的类,它几乎是我们在这里完成的INotifyPropertyChanged的实现。我在每个WPF / UWP / Silverligt / WindowsPhone解决方案中都重用了这个类,因为它是通用的。

    然后我在每个项目中创建从BindableBase派生的自定义ViewModelBase类:

    public abstract ViewModelBase : BindableBase
    {
        //project specific logic for all viewmodels. 
        //E.g in this project I want to use EventAggregator heavily:
        public virtual IEventAggregator () => ServiceLocator.GetInstance<IEventAggregator>()   
    }
    

    如果我有app,它使用基于页面的导航,我还为页面视图模型指定了基类。

    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);
        }
    }
    

答案 1 :(得分:4)

你有一些nuget包来实现MVVM

  1. MVVM light
  2. MVVM Cross
  3. 棱镜
  4. 对我来说,初学者更容易的是MVVM灯,因为它提供了一些代码示例。

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

答案 2 :(得分:2)

我喜欢this BaseVewModel它为您的视图模型提供了一个很好的简洁风格。查看之前的各种&#39;和&#39;之后&#39;比较。当然,没有必要 - 如果您不喜欢BaseViewModel提供的功能,那么就不要使用它。或者修改它,因为你有源代码。特别要注意的是,有三种不同的方法可以通过更改通知实现属性 - 选择您理解/感觉舒适的复杂程度。

答案 3 :(得分:1)

以下类可用作WPF项目中的View模型库:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    // uncomment the line below for Log4Net Logging
    //private static readonly log4net.ILog Log = Logging.For<ViewModelBase>();

    /// <summary>
    /// Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Checks if a property already matches a 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));
    }
}

ViewModel 类的示例是:

public class MyViewModel : ViewModelBase
{
    private int myProperty;
    public int MyProperty
    {
        get { return myProperty; }
        set { SetProperty(ref myProperty, value);
    }
}

答案 4 :(得分:0)

在大多数MVVM框架中,基本的ViewModel类实际上只包含很少的代码 - 通常只是INotifyPropertyChanged和一些辅助函数的实现。

查看MVVM Light的ViewModelBaseObservableObject类的源代码。 ObservableObject主要是INotifyPropertyChanged实现 - 使用lambda表达式而不是属性名称的“magic strings”。 ViewModelBase扩展了ObservableObject,主要是一种实用工具方法,用于确定您是否在Visual Studio设计器中运行

答案 5 :(得分:0)

今天重新审视这个答案,我想在为 Visual Studio 编写 MVVM 代码时提供额外的生产力改进。

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

<Window x:Class="My.Project.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:My.Project"
        mc:Ignorable="d"
        xmlns:viewmodel="clr-namespace:My.Project.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel}"

        Title="MainWindow" Height="360" Width="1000">
...
</Window>

然而,这种方法有缺点

  1. 未实现 INotifyPropertyChanged 且未实现 OnPropertyChanged 方法(如果需要)
  2. 这需要在每个 ViewModel 中完成
  3. 这是特定于 Visual Studio 的

优点:

  1. 一旦在项目中定义了 SetProperty 方法,使用 Quick Actions and Refactoring... 选项只会为您生成必要的属性和字段。如果您从 ViewModelBase 继承,这也适用。

这是由 Visual Studio 生成的 SetProperty 方法。

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;
}

答案 6 :(得分:-2)

这里有一个很好的讨论:关于这个问题的https://codereview.stackexchange.com/q/13823。采用表达式的好方法,以便在提升属性更改通知时获得类型安全。