MVVM即模型-视图-视图模型 ,是用于解耦 UI 代码和非 UI 代码的 设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。 数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。

MVVM模式由M(Model),V(View),VM(ViewModel)三部分组成,其设计模式类似为MVC开发模式。目的是解耦UI和代码,编译编辑和修改。
通常一个View对应一个ViewModel,一个ViewModel可能包含n个model。
实际应用过程中可以对View中的不同控件绑定不同的ViewModel,变成一对多的模式。

View即UI界面,wpf使用xaml编,xaml是在xml的基础上开发的,整体风格与xml有诸多类似之处。
View界面中需要通过数据绑定将具体的数据绑定到ViewModel的对应属性上。
- <DataGrid x:Name="_dgSubItemList" Grid.Row="0" Margin="10,0,10,0" ItemsSource="{Binding SubitemList}" SelectedItem="{Binding SelectedSubitem}" Style="{StaticResource MLMB.DataGridStyle}">
- <DataGrid.Columns>
- <DataGridTextColumn Header="{x:Static MUI:Strings.ID}" Binding="{Binding ID, StringFormat=\{0:D\}}" MinWidth="40"/>
- <DataGridTextColumn Header="{x:Static MUI:Strings.SubitemName}" Binding="{Binding Name}" MinWidth="100" Width="1*"/>
- <DataGridTextColumn Header="{x:Static MUI:Strings.RS}" Binding="{Binding RS}" MinWidth="80"/>
- <DataGridTextColumn Header="{x:Static MUI:Strings.RelatedGene}" Binding="{Binding RelatedGene}" MinWidth="100"/>
- DataGrid.Columns>
- DataGrid>
如上XAML中将DataGrid的ItemsSource属性绑定到列表,同时将对应列的数据绑定到列表的不同属性数据上。
<ctrls:UdButton Content="{x:Static MUI:Strings.Add}" Command="{Binding AddCommand}" CommandParameter="{Binding ElementName=_dgSubItemList}" x:Name="_btnAddSubItem"/>
按钮不设置Click事件,该有使用Command属性绑定具体的方法,进而调用,同时使用CommandParameter属性传递UI界面参数给ViewModel,以便后台可以访问View的控件进行相关操作。
如果可以一般ViewModel不建议访问View中的控件,以便完全剥离UI和后台代码,便于移植。
- <Window.DataContext>
- <vm:AddSubItemViewModel/>
- Window.DataContext>
通过设置窗体的DataContext属性绑定具体的ViewModel,此方法不需要后台实现ViewModel之后再绑定,可以直接系统实现ViewModel。
DataContext="{DynamicResource vm:AddSubItemViewModel}"
Xaml设置DataContext属性时需要单列绑定
- ///
- /// 构造函数
- ///
- public AddSubItemWindow()
- {
- InitializeComponent();
- this.DataContext = new AddSubItemViewModel();
- }
也可以在后台用用代码进行设置,再构造函数里用构造函数绑定ViewModel。
一般一个界面设定窗体的DataContext属性,不过实际使用可以对不同的孔家设置不同的DataContext属性。
ViewModel即View和Model的中间层,用于中转界面和具体实现代码。
ViewModel用于被绑定的数据需要使用INotifyPropertyChanged接口,才能做到UI界面和后台数据的实时变更通知,ViewModel数据更新后实时刷新界面View的数据。
- public class BaseNotifyPropertyChanged : INotifyPropertyChanged
- {
-
- ///
- /// 实现接口
- ///
- public event PropertyChangedEventHandler PropertyChanged;
-
- ///
- /// 属性变更事件
- ///
- /// 事件源
- public void OnPropertyChanged([CallerMemberName] string propertyName = "")
- {
- this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
配置[CallerMemberName]属性可以自动获取属性名称作为委托参数的名称,省去代码调用时需要一个个输入委托参数名称。
- ///
- /// private button enabled state.
- ///
- private bool _isBtnEnabled = true;
-
- ///
- /// Button enabled state.
- ///
- public bool IsBtnEnabled
- {
- get
- {
- return _isBtnEnabled;
- }
- set
- {
- _isBtnEnabled = value;
- OnPropertyChanged();
- }
- }
先定义一个内部私有属性,然后定义一个public属性,通过get,set获取私有类。设置属性值时调用“OnPropertyChanged()”方法通知界面刷新数据。
- ///
- /// private subitem list.
- ///
- private ObservableCollection
_subitemList = new ObservableCollection(); -
- ///
- /// Subitem list.
- ///
- public ObservableCollection
SubitemList - {
- get
- {
- return _subitemList;
- }
- set
- {
- _subitemList = value;
- OnPropertyChanged();
- }
- }
绑定列表时一般使用ObservableCollection
当然,如果不需要数据实时刷新时,可以直接绑定DataTable或者List
绑定事件需要继承ICommand类,通过ICommand实现事件。
- ///
- /// Base command class.
- ///
- ///
通过事件委托,以便跨线程调用 - public class BaseCommand : ICommand
- {
- ///
- /// Event handler
- /// 事件出发时先调用CanExecute(),然后调用
- ///
- public event EventHandler CanExecuteChanged;
- ///
- /// Execute action.
- ///
- public Action<object> DoExecute { get; set; }
-
- ///
- /// Canexecute method.
- ///
- public Func<object, bool> DoCanExecute { get; set; } = new Func<object, bool>(obj => true);
-
- ///
- /// Can execute.
- ///
- ///
- ///
- public bool CanExecute(object parameter)
- {
- //初始化时会调用一次
- return DoCanExecute?.Invoke(parameter) == true;
- }
- ///
- /// Excute.
- ///
- ///
- public void Execute(object parameter)
- {
- //实例化委托。
- DoExecute?.Invoke(parameter);
- }
-
- ///
- /// 执行委托,调用CanExecute。
- ///
- public void DoCanExecuteChanged()
- {
- CanExecuteChanged?.Invoke(this, EventArgs.Empty);
- }
- }
ICommand接头实例化时会先调用一次CanExecuteChanged,然后出发方法后先调用一次CanExecuteChanged,然后调用Execute类。
该BaseCommand类实现两个方法,然后将事件通过委托外传,ViewMode可以为具体事件绑定不同的方法。
- ///
- /// 构造函数
- ///
- public AddSubItemViewModel()
- {
- //Relate to export data method.
- ExportDataCommand = new BaseCommand()
- {
- DoExecute = new Action<object>(ExportData)
- };
- }
-
- ///
- /// Export data.
- ///
- ///
- public void ExportData(object obj)
- {
- IsBtnEnabled = false;
- FileIO.ExportData();
- }
先实现指令类,然后再构造函数中管理具体的实现方法,再在函数中调用具体的处理函数。
需要先添加NuGet包 behaviors


然后再Xaml文件中添加引用:
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
再对应的控件中添加具体方法,绑定Command:
- <behaviors:Interaction.Triggers>
- <behaviors:EventTrigger EventName="SelectionChanged">
- <behaviors:InvokeCommandAction Command="{Binding SelectedChangedCommand}" CommandParameter="{Binding ElementName=lb_Items}"/>
- behaviors:EventTrigger>
- behaviors:Interaction.Triggers>
Model类是一个个具体处理的类,包括各种处理函数等实体类,通过ViewModel进行调用。