WPF之MVVM模式
MVVM即模型-视图-视图模型 ,是用于解耦 UI 代码和非 UI 代码的 设计模式。借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。MVVM模式由M(Model),V(View),VM(ViewModel)三部分组成,其设计模式类似为MV
1.MVVM模式
MVVM即模型-视图-视图模型 ,是用于解耦 UI 代码和非 UI 代码的 设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。 数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。
MVVM模式由M(Model),V(View),VM(ViewModel)三部分组成,其设计模式类似为MVC开发模式。目的是解耦UI和代码,编译编辑和修改。
通常一个View对应一个ViewModel,一个ViewModel可能包含n个model。
实际应用过程中可以对View中的不同控件绑定不同的ViewModel,变成一对多的模式。
2.View
View即UI界面,wpf使用xaml编,xaml是在xml的基础上开发的,整体风格与xml有诸多类似之处。
2.1绑定数据
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属性绑定到列表,同时将对应列的数据绑定到列表的不同属性数据上。
2.2绑定事件
<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和后台代码,便于移植。
2.3绑定ViewModel
<Window.DataContext>
<vm:AddSubItemViewModel/>
</Window.DataContext>
通过设置窗体的DataContext属性绑定具体的ViewModel,此方法不需要后台实现ViewModel之后再绑定,可以直接系统实现ViewModel。
DataContext="{DynamicResource vm:AddSubItemViewModel}"
Xaml设置DataContext属性时需要单列绑定<Window.DataContext>属性标签,如果在Window标签中绑定DataContext属性需要绑定静态的ViewModel,否则无法正常获取ViewModel数据。
/// <summary>
/// 构造函数
/// </summary>
public AddSubItemWindow()
{
InitializeComponent();
this.DataContext = new AddSubItemViewModel();
}
也可以在后台用用代码进行设置,再构造函数里用构造函数绑定ViewModel。
一般一个界面设定窗体的DataContext属性,不过实际使用可以对不同的孔家设置不同的DataContext属性。
3. ViewModel
ViewModel即View和Model的中间层,用于中转界面和具体实现代码。
3.1 实现INotifyPropertyChanged接口
ViewModel用于被绑定的数据需要使用INotifyPropertyChanged接口,才能做到UI界面和后台数据的实时变更通知,ViewModel数据更新后实时刷新界面View的数据。
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
/// <summary>
/// 实现接口
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 属性变更事件
/// </summary>
/// <param name="Path">事件源</param>
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
配置[CallerMemberName]属性可以自动获取属性名称作为委托参数的名称,省去代码调用时需要一个个输入委托参数名称。
3.2 基本属性参数
/// <summary>
/// private button enabled state.
/// </summary>
private bool _isBtnEnabled = true;
/// <summary>
/// Button enabled state.
/// </summary>
public bool IsBtnEnabled
{
get
{
return _isBtnEnabled;
}
set
{
_isBtnEnabled = value;
OnPropertyChanged();
}
}
先定义一个内部私有属性,然后定义一个public属性,通过get,set获取私有类。设置属性值时调用“OnPropertyChanged()”方法通知界面刷新数据。
3.3 绑定列表
/// <summary>
/// private subitem list.
/// </summary>
private ObservableCollection<SubItem> _subitemList = new ObservableCollection<SubItem>();
/// <summary>
/// Subitem list.
/// </summary>
public ObservableCollection<SubItem> SubitemList
{
get
{
return _subitemList;
}
set
{
_subitemList = value;
OnPropertyChanged();
}
}
绑定列表时一般使用ObservableCollection<T>类实现,以便数据更新和刷新。
当然,如果不需要数据实时刷新时,可以直接绑定DataTable或者List<T>等数据源。
3.4 绑定事件
3.4.1 有command属性时绑定方法
绑定事件需要继承ICommand类,通过ICommand实现事件。
/// <summary>
/// Base command class.
/// </summary>
/// <remarks>通过事件委托,以便跨线程调用</remarks>
public class BaseCommand : ICommand
{
/// <summary>
/// Event handler
/// 事件出发时先调用CanExecute(),然后调用
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Execute action.
/// </summary>
public Action<object> DoExecute { get; set; }
/// <summary>
/// Canexecute method.
/// </summary>
public Func<object, bool> DoCanExecute { get; set; } = new Func<object, bool>(obj => true);
/// <summary>
/// Can execute.
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
//初始化时会调用一次
return DoCanExecute?.Invoke(parameter) == true;
}
/// <summary>
/// Excute.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
//实例化委托。
DoExecute?.Invoke(parameter);
}
/// <summary>
/// 执行委托,调用CanExecute。
/// </summary>
public void DoCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
ICommand接头实例化时会先调用一次CanExecuteChanged,然后出发方法后先调用一次CanExecuteChanged,然后调用Execute类。
该BaseCommand类实现两个方法,然后将事件通过委托外传,ViewMode可以为具体事件绑定不同的方法。
/// <summary>
/// 构造函数
/// </summary>
public AddSubItemViewModel()
{
//Relate to export data method.
ExportDataCommand = new BaseCommand()
{
DoExecute = new Action<object>(ExportData)
};
}
/// <summary>
/// Export data.
/// </summary>
/// <param name="obj"></param>
public void ExportData(object obj)
{
IsBtnEnabled = false;
FileIO.ExportData();
}
先实现指令类,然后再构造函数中管理具体的实现方法,再在函数中调用具体的处理函数。
3.4.2 将事件绑定到Command上
需要先添加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>
4. Model
Model类是一个个具体处理的类,包括各种处理函数等实体类,通过ViewModel进行调用。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)