当前位置:   article > 正文

学懂C#编程:常用框架学习(三)———学会并深入理解WPF核心之MVVM模式

学懂C#编程:常用框架学习(三)———学会并深入理解WPF核心之MVVM模式

1. 理解基本概念

WPF: WPF是微软的一个用于开发Windows客户端应用程序的框架。它提供了丰富的UI元素和样式,以及强大的数据绑定和动画支持。

MVVM(Model-View-ViewModel): MVVM是一种架构模式,它将应用程序分为三个主要部分:

  • Model:代表数据以及业务逻辑。
  • View:用户界面,用于显示数据。
  • ViewModel:充当Model和View之间的桥梁,负责处理UI逻辑和业务逻辑的分离。

2. MVVM的特点

  • 解耦:Model、View和ViewModel之间高度解耦,使得代码更易于维护和测试。
  • 可重用性:ViewModel可以独立于View被重用。
  • 易于测试:由于ViewModel不包含任何UI相关的代码,因此可以独立于UI进行测试。

 

3. 实现MVVM的关键技术
  • 依赖属性(Dependency Properties):在ViewModel中使用,使得UI可以监听其变化并自动更新。
  • 命令(ICommand接口):代替事件处理,使得UI可以调用ViewModel中的方法而无需硬编码到代码后面。
  • 数据上下文(DataContext):将ViewModel与View关联起来,使得View可以访问ViewModel中的属性和命令。

 4. 核心特性:数据绑定

        数据绑定是WPF中一个核心特性,它允许你将UI元素的属性直接与应用程序的数据源的属性连接起来。这意味着,当数据源的值发生变化时,UI会自动更新以反映这些变化,反之亦然。这大大简化了代码,提高了可维护性和灵活性。

数据绑定的基本要素:
  1. :要绑定的数据源头,通常是业务对象的属性。
  2. 目标:UI元素的属性,比如TextBox的Text属性。
  3. 路径:在源对象中指定的属性路径。
  4. 模式:绑定的方向,如OneWay(单向从源到目标)、TwoWay(双向)、OneTime(一次性)等。
  5. 转换器:可选的,用于在数据源和目标类型之间转换数据的类。
示例:
  1. <!-- 在XAML中设置数据绑定 -->
  2. <TextBox Text="{Binding Path=UserName, Mode=TwoWay}" />
  1. // 在C#代码中设置数据上下文
  2. public MainWindow()
  3. {
  4. InitializeComponent();
  5. DataContext = new UserModel { UserName = "John Doe" };
  6. }

5. 示例解析 

     下面通过简单的实例展示,让我们更容易学会和理解WPF的核心编程MVVM模式及其数据绑定特性。

假设我们有一个简单的应用程序,显示一个 Person 对象的列表,并允许用户添加新的 Person

1) 定义 Model
  1. public class Person
  2. {
  3. public string Name { get; set; }
  4. public int Age { get; set; }
  5. }
2) 定义 ViewModel
  1. //定义了一个名为MainViewModel的公共类,它实现了INotifyPropertyChanged接口。这个接口用于在属性值更改时通知绑定的UI元素。
  2. public class MainViewModel : INotifyPropertyChanged
  3. {
  4. //声明了一个私有成员people,它是一个ObservableCollection<Person>类型的集合。ObservableCollection是一个特殊的集合,当集合中的项目被添加、删除或更改时,它可以自动通知绑定的UI元素。
  5. private ObservableCollection<Person> people;
  6. //声明了一个私有成员selectedPerson,它用于存储当前选中的Person对象。
  7. private Person selectedPerson;
  8. //以下这段代码定义了一个公共属性People,它允许外部访问people集合。在setter中,如果people的值发生变化(即与新的value不相等),则更新people并通知任何绑定的UI元素该属性已更改。
  9. public ObservableCollection<Person> People
  10. {
  11. get { return people; }
  12. set
  13. {
  14. if (people != value)
  15. {
  16. people = value;
  17. OnPropertyChanged(nameof(People));
  18. }
  19. }
  20. }
  21. //类似于People属性,这里定义了SelectedPerson属性。当selectedPerson的值变化时,它会通知绑定的UI元素。
  22. public Person SelectedPerson
  23. {
  24. get { return selectedPerson; }
  25. set
  26. {
  27. if (selectedPerson != value)
  28. {
  29. selectedPerson = value;
  30. OnPropertyChanged(nameof(SelectedPerson));
  31. }
  32. }
  33. }
  34. //声明了一个RelayCommand类型的公共属性AddPersonCommand。RelayCommand是一个常用的命令实现,通常用于MVVM模式中以处理UI命令(如按钮点击)
  35. public RelayCommand AddPersonCommand { get; set; }
  36. //构造函数中,初始化了People集合和AddPersonCommand命令。AddPersonCommand被设置为执行AddPerson方法
  37. public MainViewModel()
  38. {
  39. // 初始化People集合
  40. People = new ObservableCollection<Person>
  41. {
  42. new Person { Name = "John Doe", Age = 30 },
  43. new Person { Name = "Jane Doe", Age = 25 }
  44. };
  45. // 初始化AddPersonCommand命令
  46. AddPersonCommand = new RelayCommand(AddPerson);
  47. }
  48. //AddPerson方法是一个私有方法,用于向People集合中添加一个新的Person对象。这里parameter参数没有被使用,但在某些情况下,它可能包含有关要添加的人的额外信息。
  49. private void AddPerson(object parameter)
  50. {
  51. People.Add(new Person { Name = "New Person", Age = 0 });
  52. }
  53. //这行代码声明了PropertyChanged事件,它是INotifyPropertyChanged接口的一部分。当ViewModel中的任何属性更改时,它将触发此事件。
  54. public event PropertyChangedEventHandler PropertyChanged;
  55. //OnPropertyChanged是一个受保护的方法,用于触发PropertyChanged事件。它接受一个字符串参数propertyName,该参数指定了已更改的属性的名称。PropertyChanged?.Invoke(...)是一种空值合并运算符的使用,它确保如果PropertyChanged事件不是null,则调用它。
  56. protected void OnPropertyChanged(string propertyName)
  57. {
  58. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  59. }
  60. }

程序解析:

      这段代码定义了一个名为MainViewModel的类,它是MVVM架构中的ViewModel部分,主要用于处理数据和逻辑,同时实现INotifyPropertyChanged接口以支持UI的数据绑定。下面是逐行的解释:

1、类定义:定义了MainViewModel类并让它实现了INotifyPropertyChanged接口,这意味着它能够通知UI当其属性值改变时。

2、私有字段:定义了两个私有字段,peopleselectedPerson,分别用于存储一个ObservableCollection<Person>(表示一组人)和一个当前选中的人。

3、 People属性:定义了一个公开的People属性,类型为ObservableCollection<Person>。使用了属性模式(getter和setter)。当people字段的值发生改变时,会调用OnPropertyChanged方法通知任何绑定到此属性的UI元素更新。

4、 SelectedPerson属性:定义了公开的SelectedPerson属性,类型为Person。同样,当selectedPerson字段的值改变时,会通知UI更新。

5、AddPersonCommand属性:定义了一个公开的AddPersonCommand属性,类型为RelayCommand,用于处理添加人的逻辑。

6、 构造函数MainViewModel的构造函数初始化了People集合,添加了两个默认的Person对象,并实例化了一个AddPersonCommand,将其委托给AddPerson方法。

7、 AddPerson方法:这是一个私有方法,当AddPersonCommand被执行时被调用,用于向People集合中添加一个新的Person对象。

8、 PropertyChanged事件:定义了INotifyPropertyChanged接口要求的事件,用于通知属性变更。

9、 OnPropertyChanged方法:这是一个受保护的方法,用于在属性值更改时调用,它会触发PropertyChanged事件,告知UI哪个属性发生了变化。

    总之,这个MainViewModel类管理着一组人的集合(People),并允许用户选择其中一个(SelectedPerson),同时还提供了一个命令(AddPersonCommand)来向集合中添加新的成员。这样的设计使得UI和业务逻辑分离,便于测试和维护。

 

3) 定义 View
  1. <Window x:Class="MvvmExample.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="MVVM Example" Height="300" Width="400">
  5. <Window.DataContext>
  6. <local:MainViewModel/>
  7. </Window.DataContext>
  8. <Grid>
  9. <Grid.RowDefinitions>
  10. <RowDefinition Height="*"/>
  11. <RowDefinition Height="Auto"/>
  12. </Grid.RowDefinitions>
  13. <ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" Grid.Row="0">
  14. <ListBox.ItemTemplate>
  15. <DataTemplate>
  16. <StackPanel Orientation="Horizontal">
  17. <TextBlock Text="{Binding Name}" Margin="5"/>
  18. <TextBlock Text="{Binding Age}" Margin="5"/>
  19. </StackPanel>
  20. </DataTemplate>
  21. </ListBox.ItemTemplate>
  22. </ListBox>
  23. <Button Content="Add Person" Command="{Binding AddPersonCommand}" Grid.Row="1" Margin="10"/>
  24. </Grid>
  25. </Window>
  • ItemsSource="{Binding People}":将列表框的项源绑定到ViewModel的People属性。
  • SelectedItem="{Binding SelectedPerson}":将列表框的选定项绑定到ViewModel的SelectedPerson属性。
  • <DataTemplate>:定义数据模板,用于展示Person对象。
  • <StackPanel ...>:使用堆栈面板水平排列两个文本块,分别展示PersonNameAge属性。
  • Content="Add Person":设置按钮的文本为"Add Person"。
  • Command="{Binding AddPersonCommand}":将按钮的点击命令绑定到ViewModel的AddPersonCommand,当按钮被点击时,会执行ViewModel中相应的命令逻辑。
4)定义 RelayCommand
  1. public class RelayCommand : ICommand
  2. {
  3. private readonly Action<object> execute;
  4. private readonly Predicate<object> canExecute;
  5. public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
  6. {
  7. this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
  8. this.canExecute = canExecute;
  9. }
  10. public bool CanExecute(object parameter)
  11. {
  12. return canExecute == null || canExecute(parameter);
  13. }
  14. public void Execute(object parameter)
  15. {
  16. execute(parameter);
  17. }
  18. public event EventHandler CanExecuteChanged
  19. {
  20. add { CommandManager.RequerySuggested += value; }
  21. remove { CommandManager.RequerySuggested -= value; }
  22. }
  23. }

 代码解析:

        这段代码定义了一个名为RelayCommand的类,它实现了.NET中的ICommand接口,常用于MVVM(Model-View-ViewModel)架构中,作为绑定到UI元素(如按钮)的命令,以便在用户交互时执行特定的操作。下面是逐行的解释:

  1. 类定义:定义了RelayCommand类,并指明它实现了ICommand接口。

  2. 私有字段

    • execute:一个Action<object>委托,代表命令执行时需要调用的实际操作,参数object可以携带命令执行时需要的数据。
    • canExecute:一个Predicate<object>委托,用于判断命令是否可以执行,同样接受一个object参数,返回布尔值。

3-6. 构造函数:构造函数接受两个参数:

  • execute委托,必须提供,否则抛出ArgumentNullException异常。
  • canExecute委托,默认值为null,意味着如果不提供,则默认命令总是可执行的。

7-12. CanExecute方法:这是ICommand接口的一部分,用于检查命令当前是否可以执行。如果canExecute委托存在,则调用它并传入parameter,返回其结果;如果canExecutenull,则默认返回true,表示命令始终可执行。

13-16. Execute方法:同样是ICommand接口的一部分,用于执行命令。直接调用execute委托并将参数传递给它。

17-26. CanExecuteChanged事件:这是一个特殊的事件,用于通知UI命令的可执行状态可能已改变,应当重新查询其状态。这里通过CommandManager.RequerySuggested事件来实现,当添加或移除事件处理器时,自动订阅或取消订阅这个事件。这样,每当UI需要重新评估命令状态时(比如依赖的属性变化),框架会自动调用CanExecute方法来更新UI元素(如使按钮变为可用或不可用)。

        总的来说,RelayCommand类提供了一种简洁的方式,将UI命令与后台操作逻辑解耦,方便在MVVM模式中使用命令绑定,增强了代码的可测试性和可维护性。

解释

  1. Model: Person 类表示数据模型。

  2. ViewModel: MainViewModel 类包含 People 集合和 SelectedPerson 属性,以及 AddPersonCommand 命令。People 集合使用 ObservableCollection,以便在集合更改时通知 UI。

  3. View: 在 XAML 中,通过设置 DataContextMainViewModel,将 View 与 ViewModel 关联。使用 {Binding} 语法将 ListBoxItemsSource 绑定到 People 集合,将 SelectedItem 绑定到 SelectedPerson 属性。按钮的 Command 属性绑定到 AddPersonCommand 命令。

通过这种方式,MVVM 模式使得界面与业务逻辑完全分离,提高了代码的可维护性和可测试性。数据绑定和命令机制是实现 MVVM 的关键技术。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/810965
推荐阅读
相关标签
  

闽ICP备14008679号