C# 如何在MVVM中编写ViewModelBase
我是WPF编程环境的新手。我正在尝试使用MVVM设计模式编写一个程序 我做了一些研究,读了一些与之相关的文章,很多时候我遇到了这个叫做 ViewModelBaseC# 如何在MVVM中编写ViewModelBase,c#,wpf,mvvm,viewmodel,C#,Wpf,Mvvm,Viewmodel,我是WPF编程环境的新手。我正在尝试使用MVVM设计模式编写一个程序 我做了一些研究,读了一些与之相关的文章,很多时候我遇到了这个叫做 ViewModelBase 我知道那是什么。。但是,我可以明确地我应该从哪里开始写我自己的ViewModelBase吗?喜欢真正理解正在发生的事情,而不会变得太复杂。谢谢:)您有一些nuget包来实现MVVM MVVM灯 MVVM交叉 棱镜 对我来说,对于初学者来说,MVVM light更容易,因为它提供了一些代码示例 因此,最好安装这个nuget软件包,查看生
我知道那是什么。。但是,我可以明确地我应该从哪里开始写我自己的ViewModelBase吗?喜欢真正理解正在发生的事情,而不会变得太复杂。谢谢:)您有一些nuget包来实现MVVM
因此,最好安装这个nuget软件包,查看生成的代码,如果需要,请返回给我们以获取更多解释。如果您不知道内部发生了什么,那么使用MVVM框架是毫无价值的 因此,让我们一步一步地构建您自己的ViewModelBase类
[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
操作符
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
方法(如果需要)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>