基础知识
使用基础
安装
- 使用NuGet即可下载到最新免费的MVVMLight,安装之后,会有以下几个DLL被项目引用:
其中还依赖了一个微软的DLL——Microsoft.Practices.ServiceLocation.dll。
-
在使用过程中,**可能遇到的问题:**就是可能是由于MvvmLight没有及时更新,如果将图中的CommonServiceLocator升级到最新的2.x版本之后,它所依赖的ServiceLication的名称空间变为了CommonServiceLocator,导致安装MvvmLight之后自动生成的代码就出现了错误,如果你使用了最新版本,那么就需要手动修改一下这个名称空间:
-
安装完成MvvmLight之后,系统会自动生成一个ViewModel文件夹以及MainViewModel和ViewModelLocator两个文件,并将ViewModelLocator添加到了全局资源App.xaml中:
<Application x:Class="MvvmLightDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MvvmLightDemo" StartupUri="MainWindow.xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d"
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:MvvmLightDemo.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
整体架构
重点类型一: ViewModelLocator
这个类型有什么作用,要把它放到全局资源中?
- 在View层,即XAML文件中,不必直接引用ViewModel类型了,通过Locator中的Register进行关联
- 所有的View都可以到Locator中找到自己需要的VM层,这个Locator相当于一个索引器
- Locator层返回的VM层是单例形式的,避免了由于代码问题导致的内存泄漏
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// 这个类型的用处就是包含了所有的View和ViewModel之间的关联,使得View和ViewModel不再直接引用;
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// 在Visual Studio中设计预览Xaml文件的时候,在这里注册会起作用;
SimpleIoc.Default.Register<MainViewModel>();
}
else
{
// 在运行时,在这里注册会起作用;
SimpleIoc.Default.Register<MainViewModel>();
}
// 更多的时候,默认在这里注册就可以了;
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// 这里对应的是View中DataContext需要的依赖属性;
/// 就是这个Main属性,关联起了View和ViewModel;
/// DataContext="{Binding Source={StaticResource Locator}, Path=Main}
/// </summary>
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
重点类型二:ViewModelBase
它继承自父类ObservableObject,实现了INotifyPropertyChanged和ICleanup,以实现:
- 当对象的属性发生变化的时候,会通知View层更新它的显示
- 在Page切换、标签页关闭等场景中,使用Clearup以及-=event释放不再需要的资源
使用依赖属性:
在使用的过程中,需要我们调用ViewModelBase为我们提供的Set函数在属性的Set当中调用,即可实现通知View层的效果:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
public class MainViewModel : ViewModelBase
{
private string m_LabelShow;
public string Label1Show
{
get { return m_LabelShow; }
// MvvmLight实现的Set方法,好处就是不用自己实现RaisePropertyChanged函数了;
set { Set(ref m_LabelShow, value); }
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
Label1Show = "ShowOne";
}
}
至于MvvmLight的实现原理,以下代码可以表明MvvmLight到底做了些什么:
- 它帮助我们实现了RaisePropertyChanged方法
- 在属性值发生变化的时候,就可以发出通知
/// <summary>
/// 写一个一个轻量级MVVM框架,以帮助理解MVVM到底是什么;
/// INotifyPropertyChanged接口实现了当属性发生变化的时候,可以及时发出通知
/// </summary>
public class MvvmBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 属性发生改变时调用该方法发出通知;
/// </summary>
/// <param name="propertyName">属性名称</param>
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void SetAndNotifyIfChanged<T>(string propertyName, ref T oldValue, T newValue)
{
if (oldValue == null && newValue == null) return;
if (oldValue != null && oldValue.Equals(newValue)) return;
if (newValue != null && newValue.Equals(oldValue)) return;
oldValue = newValue;
RaisePropertyChanged(propertyName);
}
}
在依赖属性的基础上使用依赖命令RelayCommand<>
RelayCommand翻译过来就是“依赖命令”,相对于依赖属性,该类型主要作用就是让ViewModel层的类型具有处理依赖命令的能力。如果一个控件中包含了一个Icommand接口,那么就可以在MvvmLight架构下的VM层使用RelayCommand对依赖命令进行实现了
以下这个例子是利用RelayCommand实现一个前进后退按钮,可以导航控制Page页:
利用MvvmLight实现的优势就是:
- 前端XMAL编写和后端ViewModel编写能够完全解耦和,互不干扰
- ViewModel端直接控制自己的所有属性
View端:
<Window x:Class="MvvmLightDemo.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:MvvmLightDemo"
xmlns:vm="clr-namespace:MvvmLightDemo.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"></RowDefinition>
<RowDefinition Height="90*"></RowDefinition>
</Grid.RowDefinitions>
<!-- 依赖命令的Binding -->
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<Button Command="{Binding NavToPrePage}">Nav To PrePage</Button>
<Button Command="{Binding NavToNextPage}">Nav To NextPage</Button>
</StackPanel>
<!-- 依赖属性的Binding -->
<Frame Content="{Binding MainWindowFrame}" Grid.Row="1" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Window>
ViewModel端:
public class MainViewModel : ViewModelBase
{
/// <summary>
/// 导航到下一页面按钮的依赖命令;
/// </summary>
public RelayCommand NavToNextPage { get; set; }
/// <summary>
/// 导航到上一页面按钮的依赖命令;
/// </summary>
public RelayCommand NavToPrePage { get; set; }
/// <summary>
/// 主页Frame;
/// </summary>
private Frame m_MainWindowFrame;
public Frame MainWindowFrame
{
get { return m_MainWindowFrame; }
set { Set(ref m_MainWindowFrame, value); }
}
/// <summary>
/// 主标签页;
/// </summary>
private Page m_MPage;
public Page MPage
{
get { return m_MPage; }
set { Set(ref m_MPage, value); }
}
/// <summary>
/// 附标签页;
/// </summary>
private Page m_AttachPage;
public Page AttachPage
{
get { return m_AttachPage; }
set { Set(ref m_AttachPage, value); }
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
NavToNextPage = new RelayCommand(NavNextPage); // 导航到下一个Page页;
NavToPrePage = new RelayCommand(NavPrePage);
MPage = new MainPage();
AttachPage = new PageTwo();
MainWindowFrame = new Frame();
MainWindowFrame.NavigationUIVisibility = System.Windows.Navigation.NavigationUIVisibility.Hidden;
MainWindowFrame.Content = MPage;
}
/// <summary>
/// 处理点击下一页;
/// </summary>
private void NavNextPage()
{
MainWindowFrame.Content = AttachPage;
}
/// <summary>
/// 处理点击上一页;
/// </summary>
private void NavPrePage()
{
MainWindowFrame.Content = MPage;
}
}
源代码可参见:https://github.com/visiontrail/CSharpKnowledge 在工程MvvmLightDemo工程当中,使用VS2015/VS2017编译