Lie*_*ero 72
如果您不知道内部发生了什么,那么使用MVVM框架是没有价值的.
让我们一步一步地构建您自己的ViewModelBase类.
ViewModelBase是所有视图模型的通用类.让我们将所有常见逻辑移到这个类.
你的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));
}
}
Run Code Online (Sandbox Code Playgroud)
该[CallerMemberName]
属性不是必需的,但它允许你写:
OnPropertyChanged();
而不是OnPropertyChanged("SomeProperty");
,所以你将避免代码中的字符串常量.例:
public string FirstName
{
set
{
_firtName = value;
OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
}
get{ return _firstName;}
}
Run Code Online (Sandbox Code Playgroud)
请注意,OnPropertyChanged(() => SomeProperty)
不再推荐这个,因为我们nameof
在C#6中有操作员.
通常的做法是实现调用PropertyChanged的属性,如下所示:
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
Run Code Online (Sandbox Code Playgroud)
让我们在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;
}
Run Code Online (Sandbox Code Playgroud)
它只是PropertyChanged
在属性值更改并返回true时触发事件.当值未更改并且返回false时,它不会触发事件.基本思想是,该SetProperty
方法是虚拟的,您可以在更具体的类中扩展它,例如触发验证,或通过调用PropertyChanging
事件.
这很漂亮.这是您在此阶段应包含的所有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>()
}
Run Code Online (Sandbox Code Playgroud)
如果我有app,它使用基于页面的导航我也为页面视图模型指定基类.
public abstract PageViewModelBase : ViewModelBase
{
//for example all my pages has title:
public string Title {get; private set;}
}
Run Code Online (Sandbox Code Playgroud)
我可以有另一个类用于对话框:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
下面的类可以用作 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));
}
}
Run Code Online (Sandbox Code Playgroud)
ViewModel类的一个例子是:
public class MyViewModel : ViewModelBase
{
private int myProperty;
public int MyProperty
{
get { return myProperty; }
set { SetProperty(ref myProperty, value);
}
}
Run Code Online (Sandbox Code Playgroud)
为了便于编写,可以使用以下代码段:
<?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>
Run Code Online (Sandbox Code Playgroud)
您有一些nuget包来实现MVVM
对我来说,MVVM light对初学者来说比较容易,因为它提供了一些代码示例。
因此,最好是安装此nuget软件包,查看生成的代码,如果需要,请联系我们以获取更多说明。