【问题标题】:How to write a ViewModelBase in MVVM如何在 MVVM 中编写 ViewModelBase
【发布时间】:2016-07-09 01:45:09
【问题描述】:

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

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

的东西

视图模型库

我知道它是什么.. 但是我可以具体知道 我应该从哪里开始 以便能够写出我自己的 ViewModelBase 吗?就像......真正了解正在发生的事情而不会变得太复杂。谢谢你:)

【问题讨论】:

  • 在大多数情况下,它只是 OnNotificationChangedRaisePropertyChange 方法的基本实现,但在许多初学者和纯粹主义者(没有 MVVM 框架)教程中应该很容易找到它。出于教程目的,这很好,如果你想编写一个复杂的应用程序,你最好使用一个成熟的框架。 INPC 和 ICommand 只是 MVVM 的一小部分
  • 要详细说明@Tesng 的评论,您可以使用PropertyChanged.Fody 来实现INotifyPropertyChanged 部分。这样代码会更简洁,您不必扩展类。
  • 谢谢大家!欣赏它:)

标签: c# wpf mvvm viewmodel


【解决方案1】:

你有一些 nuget 包来实现 MVVM

  1. MVVM 灯
  2. MVVM 交叉
  3. 棱镜

对我来说,对于初学者来说更容易的是 MVVM light,因为它提供了一些代码示例。

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

【讨论】:

  • 我本来想谈谈 NuGet,但我看到了你关于 “看看生成的代码,如果需要,请返回给我们以获得更多解释”。 :)
  • 哈哈... icic... thx man!将尝试对此进行调查:) 肯定会回来提出更多问题。
  • Prism 不是 mvvm 框架,算了。 Prism.mvvm 确实是轻量级的。
  • 从 nuget 包开始:我使用 PropertyChanged.Fody(使用 Fody Weaver)。不需要基类的子类化,要么实现 InotifyPropertichanged,要么向类添加属性(AddnotifyPropertychangedinterface)。编译器生成所有需要的样板,它有一些不错的特性,例如在类中的任何其他 Prop 上调用 Event,您可以在整个类中使用 Autoproperties。
【解决方案2】:

如果你不知道里面发生了什么,那么使用 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); }
    }
    

    让我们在视图模型库中定义 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 事件。

这很漂亮。这是您的 ViewModelBase 在此阶段应包含的所有内容。其余的取决于您的项目。例如,您的应用程序使用基于页面的导航,并且您编写了自己的 NavigationService 来处理来自 ViewModel 的导航。因此,您可以将 NavigationService 属性添加到您的 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>()   
}

如果我有使用基于页面导航的应用程序,我还会为页面视图模型指定基类。

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

【讨论】:

  • 人...谢谢分享!它确实让我更好地理解了这件事的全部意义,哈哈……感谢:)
  • 可以肯定地说,您写 "BinableBase" 的地方实际上是指 "BindableBase"?我会自己编辑帖子,但由于代码中有错字,而且因为“你永远不知道”,我想我会把它留给你。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这篇文章没有直接回答手头的问题。它应该是评论,或者您应该确保在帖子本身中包含所有重要细节,仅将链接用作补充参考。
    • 请编辑您的答案。我真的很想看到 BaseViewModel 的干净变体。
    【解决方案5】:

    以下类可用作 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));
        }
    }
    

    ViewModel 类的一个例子是:

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

    为了写作方便,下面可以使用snippet

    <?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>  
    

    完整代码可从here下载。

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-30
        • 2013-02-12
        • 1970-01-01
        • 2021-01-18
        • 1970-01-01
        • 2015-09-18
        • 2023-04-06
        相关资源
        最近更新 更多