MVVM - 爱奇艺UWP客户端应用与实践

我们在开发爱奇艺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);
}

参考文献

爱奇艺Win10专业版客户端

  • 界面操作简捷,完美支持触屏设备,完美适配各种屏幕
  • 无弹窗,无后台进程
  • 支持杜比音效,杜比视界,带来更佳观影体验
  • 打开微软删掉商店搜索“爱奇艺”在这里插入图片描述
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值