我们在开发爱奇艺UWP客户端的过程中广泛的使用了MVVM模式,下面简单的介绍一下MVVM模式。
MVVM模式定义
MVVM包括三个核心模块:Model,View,View-Model
Model
-
Model是要处理的实际数据。
-
但不包括处理这些数据的操作或服务。
-
也不是用来把数据转换成某种格式来方便UI显示的。数据转换可以在ViewModel中处理,也可以定义Converter对数据进行转换。
-
业务逻辑通常独立于Model,封装成其他类来操作Model。
-
Model是易于复用的,Model可以在多个ViewModel中使用。
-
要保持Model的干净是具有挑战的。
例如:
-
业务逻辑对象,如User。
-
数据交换对象。
-
CLR元子对象,int, long, string。
-
自动产生的实体或代理对象,例如与数据库表对应的实体类,用来解析JSON或XML等数据的实体类。
View-Model
-
View和Model的中间层,通常称为View’s Model
-
给View提供ICommand属性。
-
提供其他属性管理View的状态,如Button是否可见(Visibility)状态。
-
调用其他服务获取或操作Model。
-
聚合一个或多个Model,通过Property的方式提供给View使用。
-
实现INotifyPropertyChanged,以满足UI绑定更新的需要。
-
对于集合,使用ObservableCollection等
View
- 包含主题,样式,UI布局,控件,数据绑定,事件,已经其他与用户之间的交互。
- 避免过多的逻辑代码,这些代码尽量放入ViewModel
- 通常View是用XAML来定义,有时可以使用少量代码。
- 通过数据绑定或者方法调用从View Model中获取数据。
- UI控件上的Command属性可以绑定到View Model的ICommand属性上。
- 可以通过绑定到ViewModel的属性上对UI控件的状态进行控制。
View和View-Model的关系
- View和View Model通过数据绑定,方法调用,属性,事件等进行关联。
- View Model导出Model给View,也需要定义View需要的其他状态信息,如IsAllowLogin。
- View需要管理UI Events。
- ViewModel中的Model和属性通过双向绑定进行数据更新。
ViewModel和Model的关系
- View Model导出Model,或者通过属性提供给View进行绑定。
- View Model包括服务接口,配置数据接口等,以便获取View所需要的数据。
MVVM的好处
- 开发和设计可以分开独立工作,设计师可以通过模拟数据对UI进行调试。
- View Model和Model可以对立于View的代码进行单元测试。
- 可以重新设计UI并重用View Model和Model的代码。
- View Model做为View和Model之间的适配器,可以避免View的变更而需要对Model做出修改。
MVVM的性能问题
- 避免过多的中间过渡数据,这些数据会导致过多的GC,从而影响性能。
- 数据绑定问题,数据绑定会需要额外的动态内存分配,导致UI性能降低。使用{x:Bind}来替代Binding改善UI性能。
- 通常把Button.Click绑定到View Model的Icommand属性上,这样会造成额外的开销。建议在后台代码使用Click Event,在事件中调用View Model的方法。
- UI中经常把一些不展示给用户的部分的Visibility属性绑定到View Model的属性上来控制这部分UI的可见性。推荐使用x:Load或者x:DeferLoadStrategy来延迟这部分UI的加载。
Sample
下面写一个简单例子:
- Model
public class LoginModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private string userName = "Tester";
public string UserName
{
get { return userName; }
set
{
userName = value;
OnPropertyChanged(nameof(UserName));
}
}
private string password = string.Empty;
public string Passowrd
{
get { return password; }
set
{
password = value;
OnPropertyChanged(nameof(Passowrd));
}
}
}
- **ViewModel**
- 在ViewModel中添加LoginModel的实例。
- 在ViewModel中添加LoginCommand属性。
```java
public class LoginViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private LoginModel loginModel = new LoginModel();
private ICommand loginCommand;
public ICommand LoginCommand
{
get { return loginCommand; }
}
public LoginViewModel()
{
loginCommand = new LoginCommand(loginModel);
}
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
- Command
- 把LoginModel传入LoginCommand,通过判断LoginModel的UserName和Password判断LoginCommand是否可执行。
public class LoginCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private LoginModel model;
public LoginCommand(LoginModel model)
{
this.model = model;
this.model.PropertyChanged += Model_PropertyChanged;
}
private void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(model.UserName) ||
e.PropertyName == nameof(model.Passowrd))
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
return !string.IsNullOrEmpty(this.model.UserName) &&
!string.IsNullOrEmpty(this.model.Passowrd);
}
public async void Execute(object parameter)
{
MessageDialog dlg = new MessageDialog($"Login with Name:{this.model.UserName} Password:{this.model.Passowrd}", "Login");
await dlg.ShowAsync();
}
}
- View.xaml
- 通过Data Binding,把用户名和密码的数据绑定到LoginModel的UserName和Password属性上。
- Button的Command绑定到LoginViewModel的LoginCommand属性上
<Page
x:Class="ArchitectureTutorial.MVVM.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ArchitectureTutorial.MVVM"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="登录"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Margin="12"
FontSize="18" />
<TextBlock Text="登录"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Margin="12"
FontSize="18" />
<TextBlock Text="密码"
Margin="6"
HorizontalAlignment="Right"
Grid.Row="2" />
<PasswordBox Grid.Row="2"
Password="{x:Bind viewModel.LoginModel.Passowrd, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="6"
Grid.Column="1"
Width="200" />
<Button x:Name="loginButton"
Grid.Row="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Margin="12"
Width="120"
Height="32"
Command="{x:Bind viewModel.LoginCommand}"
CommandParameter="{x:Bind viewModel.LoginModel}"
Content="登录" />
</Grid>
</Page>
- View.cs
- 定义LoginViewModel的实例。
public sealed partial class LoginPage : Page
{
private LoginViewModel viewModel = new LoginViewModel();
public LoginPage()
{
this.InitializeComponent();
}
}
单元测试
- 创建添加UnitTest工程
- 每次对代码逻辑进行修改,都可以运行测试用例,检查逻辑是否有错
public void TestLogin()
{
LoginViewModel viewModel = new LoginViewModel();
viewModel.LoginModel.UserName = "Test";
viewModel.LoginModel.Passowrd = "123";
Assert.IsTrue(viewModel.LoginCommand.CanExecute(null));
viewModel.LoginCommand.Execute(null);
}
参考文献
-
MVVM Best Practice
https://www.youtube.com/watch?v=ysWK4e2Mtco -
Model-View-ViewModel(MVVM) Explained
https://www.codeproject.com/articles/100175/model-view-viewmodel-mvvm-explained -
Fundamental MVVM
https://visualstudiomagazine.com/articles/2011/08/15/fundamental-mvvm.aspx -
MVVM Performance Tips
https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/mvvm-performance-tips
爱奇艺Win10专业版客户端
- 界面操作简捷,完美支持触屏设备,完美适配各种屏幕
- 无弹窗,无后台进程
- 支持杜比音效,杜比视界,带来更佳观影体验
- 打开微软删掉商店搜索“爱奇艺”