wpf 如何在MVVM中编写ViewModelBase

erhoui1w  于 2023-02-13  发布在  其他
关注(0)|答案(7)|浏览(263)

我是WPF编程环境的新手,我正在尝试使用MVVM设计模式编写程序。
我做了一些研究,读了一些相关的文章,很多时候我遇到了一个叫做
查看模型库
我知道它是什么......但是我可以具体地知道我应该从哪里开始才能写出我自己的ViewModelBase吗?就像......真正理解正在发生的事情而不变得太复杂。谢谢:)

um6iljoc

um6iljoc1#

如果你不知道MVVM框架里面是怎么回事,那么使用MVVM框架就毫无意义。
因此,让我们一步一步地构建您自己的ViewModelBase类。

  1. ViewModelBase是所有视图模型的公共类。让我们把所有公共逻辑移到这个类中。
    1.您的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
     {
         _firstName = value;
         OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
     }
     get{ return _firstName;}
 }

请注意,不再推荐使用OnPropertyChanged(() => SomeProperty),因为我们在C# 6中有nameof操作符。
1.实现调用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;
 }

它只是在属性值改变时触发PropertyChanged事件并返回true。当属性值没有改变时它不触发事件并返回false。基本思想是,SetProperty方法是虚拟的,你可以在更具体的类中扩展它,例如触发验证,或者通过调用PropertyChanging事件。
这很不错。这就是ViewModelBase在此阶段应包含的全部内容。其余内容取决于您的项目。例如,您的应用使用基于页面的导航,并且您编写了自己的NavigationService用于处理来自ViewModel的导航。因此,您可以将NavigationService属性添加到ViewModelBase类,以便您可以在需要时从所有视图模型访问它。
为了获得更多的可重用性并保持SRP,我有一个名为BindableBase的类,它几乎是INotifyPropertyChanged的实现,就像我们在这里所做的那样。我在每个WPF/UWP/Silverligt/WindowsPhone解决方案中重用这个类,因为它是通用的。
然后,在每个项目中,我创建自定义ViewModelBase类派生自BindableBase:

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

inn6fuwd2#

以下类可用作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下载。

mpgws1up

mpgws1up3#

您有一些核心包来实现MVVM

  1. MVVM灯
  2. MVVM交叉
    1.棱镜
    对我来说,对于初学者来说更容易的是MVVM轻,因为它提供了一些代码样本。
    所以最好是安装这个nuget软件包,看看生成的代码,如果需要的话,可以返回给我们更多的解释。
eufgjt7s

eufgjt7s4#

在大多数MVVM框架中,ViewModel基类实际上包含很少的代码-通常只是INotifyPropertyChanged和一些帮助函数的实现。
查看MVVM Light的ViewModelBase和ObservableObject类的源代码。ObservableObject主要是INotifyPropertyChanged实现-使用lambda表达式而不是“魔术字符串”作为属性名称。ViewModelBase扩展了ObservableObject,主要是一个实用方法,用于确定您是否在Visual Studio设计器中运行

1zmg4dgp

1zmg4dgp5#

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

zvokhttg

zvokhttg6#

为了今天重新讨论这个问题,我想在为Visual Studio编写MVVM代码时提供额外的生产力改进。
VisualStudio中的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方法未实现(如果需要)
    1.这需要在每个ViewModel中完成
    1.这是特定于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;
}
uyto3xhc

uyto3xhc7#

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using System.Runtime.CompilerServices;
using System.ComponentModel;
namespace CollectionViewMVVM.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
    /*Referencia: https://www.youtube.com/watch?v=PXEt1esjnZ0&ab_channel=Codigo369*/
    public INavigation Navigation;
    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnpropertyChanged([CallerMemberName] string nombre = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nombre));
    }

    private ImageSource foto;
    public ImageSource Foto
    {
        get { return foto; }
        set
        {
            foto = value;
            OnpropertyChanged();
        }
    }
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public async Task DisplayAlert(string title, string message, string cancel)
    {
        await Application.Current.MainPage.DisplayAlert(title, message, cancel);
    }

    public async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
    {
        return await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if(EqualityComparer<T>.Default.Equals(field, value))
        {
            return false;
        }
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    /*Ejemplo para declarar entrys*/
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            SetProperty(ref _title, value);
        }
    }
    /*Para tipos bool*/
    private bool _isBusy;
    public bool IsBusy
    {
        get { return _isBusy; }
        set
        {

            SetProperty(ref _isBusy, value);
        }
    }

    protected void SetValue<T>(ref T backingFielded, T value, [CallerMemberName] string propertyName = null)
    {
        if(EqualityComparer<T>.Default.Equals(backingFielded, value))
        {
            return;
        }
        backingFielded = value;
        OnPropertyChanged(propertyName);
    }
}

}

相关问题