MVVM 框架
- MVVM
MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。
- 数据驱动
在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。
- 低耦合度
MVVM模式中,数据是独立于UI的。
数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。
- 更新UI
在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。
- View
View层做的就是和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过数据绑定实现,尽量在ViewModel里面做(更新绑定的数据源即可),Activity要做的事就是初始化一些控件(如控件的颜色,添加RecyclerView的分割线),View层可以提供更新UI的接口(但是我们更倾向所有的UI元素都是通过数据来驱动更改UI),View层可以处理事件(但是我们更希望UI事件通过Command来绑定)。简单地说:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开。
- ViewModel
ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作(这些数据绑定在相应的控件上会自动去更改UI)。同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此我们通过BindingAdapter对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把你可能需要的数据带给你,这使得我们在ViewModel层处理事件的时候只需要关心处理数据就行了,具体见MVVM Light Toolkit 使用指南的Command部分。再强调一遍:ViewModel 不做和UI相关的事。
- Model
Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义实体对象的行为截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务。Model包括实体模型(Bean)、Retrofit的Service ,获取网络数据接口,本地存储(增删改查)接口,数据变化监听等。Model提供数据获取接口供ViewModel调用,经数据转换和操作并最终映射绑定到View层某个UI元素的属性上。
- 下面是一个简单的增、删、查、改的例子:
创建好WPF项目后,首先要在引用中添加MvvmLigth框架(在管理NuGet程序包中搜索MVVM)如下图:
然后它会自动生成一个ViewModel文件夹,里面有两个类,把其它多余的类删除,留下MainViewModel类即可,如下图:
还有它会在App.xaml文件里自动生成一些内容,要把内容删除,如下图:
下面是主页的界面代码:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="搜索条件" VerticalAlignment="Center" Margin="10,0,0,0"/>
<TextBox Width="200" Height="25" Text="{Binding Search}" Margin="10,0,0,0" />
<Button Content="查找" Command="{Binding QueryCommand}" Width="70" Height="25" Margin="10,0,0,0"/>
<Button Content="重置" Command="{Binding ResetCommand}" Width="70" Height="25" Margin="10,0,0,0"/>
<Button Content="新增" Command="{Binding AddCommand}" Width="70" Height="25" Margin="10,0,0,0"/>
</StackPanel>
<DataGrid Grid.Row="1" ColumnWidth="*" AutoGenerateColumns="False" ItemsSource="{Binding GridModelList}" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="序号" Binding="{Binding Id}"/>
<DataGridTextColumn Header="姓名" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="修改" Height="25" Width="60" Foreground="Black" Background="White"
CommandParameter="{Binding Id}" Command="{Binding DataContext.EditCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGrid}}"/>
<Button Content="删除" Height="25" Width="60" Foreground="White" Background="Red"
CommandParameter="{Binding Id}" Command="{Binding DataContext.DelCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGrid}}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
下面是主页面后台的代码:
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel viewModel = new MainViewModel();
viewModel.Query();
this.DataContext = viewModel;
}
}
然后创建一个类MainViewModel,也就是上面实例化的MainViewModel viewModel = new MainViewModel();,在MainViewModel继承ViewModelBase,如下图:
下面是详细代码:
public class MainViewModel : ViewModelBase
{
/// <summary>
/// 构造函数
/// </summary>
public MainViewModel()
{
localDb = new localDb();
//初始化(查询)命令
QueryCommand = new RelayCommand(Query);
//初始化(重置)命令
ResetCommand = new RelayCommand(()=> {Search = string.Empty;this.Query();});
//初始化(修改)命令
EditCommand = new RelayCommand<int>(t => Edit(t));
//初始化(删除)命令
DelCommand = new RelayCommand<int>(t => Del(t));
//初始化(新增)命令
AddCommand = new RelayCommand(Add);
}
localDb localDb;
#region 声明按钮命令
//声明查询按钮命令
public RelayCommand QueryCommand { set; get; }
//声明重置按钮命令
public RelayCommand ResetCommand { set; get; }
//声明修改按钮命令
public RelayCommand<int> EditCommand { set; get; }
//声明删除按钮命令
public RelayCommand<int> DelCommand { set; get; }
//声明新增按钮命令
public RelayCommand AddCommand { set; get; }
#endregion
//私有储存数据的变量
private string search = string.Empty;
//提供被访问的属性
public string Search
{
get { return search; }//读取数据
set { search = value;RaisePropertyChanged();}//写入或修改数据
}
/// <summary>
/// 数据集
/// </summary>
private ObservableCollection<Student> gridModelList;
public ObservableCollection<Student> GridModelList
{
get { return gridModelList; }
set { gridModelList = value;RaisePropertyChanged(); }
}
/// <summary>
/// 数据查询
/// </summary>
public void Query()
{
var model = localDb.GetStudentsByName(Search);
GridModelList = new ObservableCollection<Student>();
if (model !=null)
{
model.ForEach(arg =>
{
GridModelList.Add(arg);
});
}
}
/// <summary>
/// 数据修改
/// </summary>
/// <param name="id"></param>
public void Edit(int id)
{
var model = localDb.GetStudentById(id);
if (model !=null)
{
UserView userView = new UserView(model);
var r = userView.ShowDialog();
if (r.Value)
{
var newModel = GridModelList.FirstOrDefault(t => t.Id == model.Id);
if (newModel !=null)
{
newModel.Name = model.Name;
}
}
}
}
/// <summary>
/// 数据删除
/// </summary>
/// <param name="id"></param>
public void Del(int id)
{
var model = localDb.GetStudentById(id);
if (model != null)
{
var r = MessageBox.Show($"确认删除当前用户:{model.Name}?","操作提示",MessageBoxButton.YesNo,MessageBoxImage.Question);
if (r==MessageBoxResult.Yes)
{
localDb.DelStudent(model.Id);
this.Query();
}
}
}
/// <summary>
/// 数据新增
/// </summary>
public void Add()
{
Student student = new Student();
UserView userView = new UserView(student);
var r = userView.ShowDialog();
if (r.Value)
{
student.Id = GridModelList.Max(t => t.Id) + 1;
localDb.AddStudent(student);
this.Query();
}
}
}
在创建一个Model文件夹,里面创建一个Student类,同时继承ViewModelBase代码如下:
public class Student:ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { name = value; RaisePropertyChanged();}
}
private int id;
public int Id
{
get { return id; }
set { id = value; RaisePropertyChanged(); }
}
}
再创建一个DB文件夹,里面创建一个localDb类,用来模拟数据生成,还有增删查改的方法,代码如下:
public class localDb
{
public localDb()
{
Init();
}
private List<Student> Student;
private void Init()
{
Student = new List<Student>();
for (int i = 0; i < 30; i++)
{
Student.Add(new Student()
{
Id = i,
Name = $"Sample{i}"
});
}
}
public List<Student> GetStudents()
{
return Student;
}
public void AddStudent(Student student)
{
Student.Add(student);
}
public void DelStudent(int Id)
{
var model = Student.FirstOrDefault(t => t.Id == Id);
if (model !=null)
{
Student.Remove(model);
}
}
public List<Student> GetStudentsByName(string name)
{
return Student.Where(q => q.Name.Contains(name)).ToList();
}
public Student GetStudentById(int id)
{
var model = Student.FirstOrDefault(t => t.Id == id);
if (model !=null)
{
return new Student()
{
Id = model.Id,
Name = model.Name
};
}
return null;
}
}
还有再创建一个Views文件夹,里面创建一个UserView窗体,用来实现用用编辑用户信息新增页面,代码如下:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition />
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<TextBlock Text="编辑用户信息" FontSize="30" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0,0,0"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="姓名" Margin="0,0,10,0" FontSize="16" />
<TextBox Width="200" Height="25" Text="{Binding Model.Name}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
<Button Content="确定" Width="70" Height="25" Click="Button_Click"/>
<Button Content="取消" Width="70" Height="25" Margin="10,0,10,0" Click="Button_Click_1"/>
</StackPanel>
</Grid>
后台:
/// <summary>
/// UserView.xaml 的交互逻辑
/// </summary>
public partial class UserView : Window
{
public UserView(Student stu)
{
InitializeComponent();
this.DataContext = new
{
Model = stu
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}
运行效果如何下图: