基本常识

开发/学习环境的准备

  • VS2019
  • Microsoft Prism 大框架支持MVVM PrismLibrary
  • Microsoft Blend SDK

VS2019已经集成4.0

必要知识的准备

  • 熟悉Data Binding 和Dependency Property
  • 了解WPF中的命令(知道ICommand接口即可)
  • 熟悉Lambda表达式

创建Code Snippet

  • Code Snippet使用

propdp propfull class等

  • Code Snippet创建

Tools-Code Snippet Manager
改写propfull模板,当属性发生变更时,进行通知:
此模板创建的RaisePropertyChanged()方法需要用下面MVVM设计模式实现:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  3. <CodeSnippet Format="1.0.0">
  4. <Header>
  5. <Title>propn</Title>
  6. <Shortcut>propn</Shortcut>
  7. <Description>属性和支持字段的代码片段,属性改变通知</Description>
  8. <Author>Albert</Author>
  9. <SnippetTypes>
  10. <SnippetType>Expansion</SnippetType>
  11. </SnippetTypes>
  12. </Header>
  13. <Snippet>
  14. <Declarations>
  15. <Literal>
  16. <ID>type</ID>
  17. <ToolTip>属性类型</ToolTip>
  18. <Default>int</Default>
  19. </Literal>
  20. <Literal>
  21. <ID>property</ID>
  22. <ToolTip>属性名</ToolTip>
  23. <Default>MyProperty</Default>
  24. </Literal>
  25. <Literal>
  26. <ID>field</ID>
  27. <ToolTip>支持此属性的变量</ToolTip>
  28. <Default>myVar</Default>
  29. </Literal>
  30. </Declarations>
  31. <Code Language="csharp"><![CDATA[private $type$ $field$;
  32. public $type$ $property$
  33. {
  34. get { return $field$;}
  35. set
  36. {
  37. $field$ = value;
  38. this.RaisePropertyChanged("$property$");
  39. }
  40. }
  41. $end$]]>
  42. </Code>
  43. </Snippet>
  44. </CodeSnippet>
  45. </CodeSnippets>

MVVM设计模式

MVVM = Model-View-ViewModel,技术主要集中在View和ViewModel之间。
image.png

为什么要使用MVVM模式

  • 团队层面:统一思维方式和实现方法
  • 架构层面:稳定,解耦,富有禅意
  • 代码层面:可读,可测,可替换

Prisim包含MVVM,模块化,依赖注入
适用范围:企业级,不适于游戏开发。

什么是Model

  • 现实世界中对象抽象结果

WPF 数据操作,即模型操作。

什么是View和ViewModel

  • View = UI
  • ViewModel = Model for View
  • ViewModel与View的沟通:
    • 传递数据—数据属性
    • 传递操作—命令属性

案例讲解

初级案例

  • NotificationObject与数据属性
  • DelegateCommand与命令属性
  • View与ViewModel的交互(技术难点)

View层:

  1. <Window x:Class="WPFMVVM.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:WPFMVVM"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="450" Width="800">
  9. <Grid>
  10. <Button x:Name="btn_Save" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="792" Height="32"/>
  11. <TextBox x:Name="tb1" HorizontalAlignment="Left" Height="25" Margin="0,53,0,0" TextWrapping="Wrap" Text="{Binding Input1}" VerticalAlignment="Top" Width="792"/>
  12. <TextBox x:Name="tb2" HorizontalAlignment="Left" Height="25" Margin="0,106,0,0" TextWrapping="Wrap" Text="{Binding Input2}" VerticalAlignment="Top" Width="792"/>
  13. <TextBox x:Name="tb3" HorizontalAlignment="Left" Height="25" Margin="0,158,0,0" TextWrapping="Wrap" Text="{Binding Result}" VerticalAlignment="Top" Width="792"/>
  14. <Button x:Name="btn_Add" Content="Add" HorizontalAlignment="Left" VerticalAlignment="Top" Width="792" Height="32" Margin="0,210,0,0" Command="{Binding AddCommand}"/>
  15. </Grid>
  16. </Window>
  17. public partial class MainWindow : Window
  18. {
  19. public MainWindow()
  20. {
  21. InitializeComponent();
  22. this.DataContext = new MainWindowViewModel();//此句不可省略
  23. }
  24. }


(1)NotificationObjects与数据属性**

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace WPFMVVM.ViewModels {
  8. //ViewModel的基类
  9. class NotificationObjects:INotifyPropertyChanged {
  10. public event PropertyChangedEventHandler PropertyChanged;
  11. public void RaisePropertyChanged(string propertyName) {
  12. if (this.PropertyChanged != null) {
  13. this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
  14. }
  15. }
  16. }
  17. }

(2)Commands与命令属性

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Input;
  7. namespace WPFMVVM.Commands {
  8. class DelegateCommand : ICommand {
  9. public Action<object> ExecuteAction { get; set; }
  10. public Func<object,bool> CanExecuteFunc { get; set; }
  11. public event EventHandler CanExecuteChanged;//当命令能够执行时,有机会通知一下命令的调用者
  12. /// <summary>
  13. /// 命令呼叫者,判断命令能不能执行
  14. /// </summary>
  15. /// <param name="parameter"></param>
  16. /// <returns></returns>
  17. public bool CanExecute(object parameter) {
  18. if (this.CanExecuteFunc == null) {
  19. return true;
  20. }
  21. return this.CanExecuteFunc(parameter);
  22. }
  23. /// <summary>
  24. /// 当命令执行的时候做什么事情
  25. /// </summary>
  26. /// <param name="parameter"></param>
  27. public void Execute(object parameter) {
  28. if (this.ExecuteAction == null) {
  29. return;
  30. }
  31. this.ExecuteAction(parameter); //这个命令把要执行的事情,委托给了ExecuteAction这个委托所指向的方法
  32. }
  33. }
  34. }

(3)建模MainWindowViewModel

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using WPFMVVM.Commands;
  7. namespace WPFMVVM.ViewModels {
  8. class MainWindowViewModel:NotificationObjects {
  9. //建模,需要三个数据属性,两个命令属性
  10. private double input1;
  11. public double Input1 {
  12. get { return input1; }
  13. set {
  14. input1 = value;
  15. this.RaisePropertyChanged("Input1");
  16. }
  17. }
  18. private double input2;
  19. public double Input2 {
  20. get { return input2; }
  21. set {
  22. input2 = value;
  23. this.RaisePropertyChanged("Input2");
  24. }
  25. }
  26. private double result;
  27. public double Result {
  28. get { return result; }
  29. set {
  30. result = value;
  31. this.RaisePropertyChanged("Result"); //这个字符串必须跟属性的值相同
  32. }
  33. }
  34. public DelegateCommand AddCommand { get; set; }
  35. private void Add(object parameter) {
  36. this.Result = this.Input1 + this.Input2;
  37. }
  38. public MainWindowViewModel() {
  39. this.AddCommand = new DelegateCommand();
  40. this.AddCommand.ExecuteAction = new Action<object>(this.Add);//ExecuteAction委托变量
  41. }
  42. }
  43. }

进阶案例

  • 使用Microsoft Prism
  • 餐馆点餐系统Demo

ViewModels层

  1. using CrazyElephant.Models;
  2. using Microsoft.Practices.Prism.ViewModel;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace CrazyElephant.ViewModels {
  9. class DishMenuItemViewModel:NotificationObject {
  10. public Dish Dish { get; set; } //ViewModel有一个方式 另一种方式是一个,Dish派生自NotificationObject
  11. //DishMenuItemViewModel派生自Dish
  12. private bool isSelected; //通知界面是否改变
  13. public bool IsSelected {
  14. get { return isSelected; }
  15. set {
  16. isSelected = value;
  17. this.RaisePropertyChanged("IsSelected");
  18. }
  19. }
  20. }
  21. }
  22. using CrazyElephant.Models;
  23. using CrazyElephant.Services;
  24. using Microsoft.Practices.Prism.Commands;
  25. using Microsoft.Practices.Prism.ViewModel;
  26. using System;
  27. using System.Collections.Generic;
  28. using System.Linq;
  29. using System.Text;
  30. using System.Threading.Tasks;
  31. using System.Windows;
  32. namespace CrazyElephant.ViewModels {
  33. class MainWindowViewModel:NotificationObject {
  34. public DelegateCommand PlaceOrderCommand { get; set; }
  35. public DelegateCommand SelectMenuItemCommand { get; set; }
  36. private int count;
  37. public int Count {
  38. get { return count; }
  39. set {
  40. count = value;
  41. this.RaisePropertyChanged("Count");
  42. }
  43. }
  44. private Restaurant restaurant;
  45. public Restaurant Restaurant {
  46. get { return restaurant; }
  47. set {
  48. restaurant = value;
  49. this.RaisePropertyChanged("Restaurant");
  50. }
  51. }
  52. private List<DishMenuItemViewModel> dishMenu;
  53. public List<DishMenuItemViewModel> DishMenu {
  54. get { return dishMenu; }
  55. set {
  56. dishMenu = value;
  57. this.RaisePropertyChanged("DishMenu");
  58. }
  59. }
  60. public MainWindowViewModel() {
  61. this.LoadRestaurant();
  62. this.LoadDishMenu();
  63. this.PlaceOrderCommand = new DelegateCommand(new Action(this.PlaceOrderCommandExecute));
  64. this.SelectMenuItemCommand = new DelegateCommand(new Action(this.SelectMenuItemExecute));
  65. }
  66. private void LoadRestaurant() {
  67. this.Restaurant = new Restaurant();
  68. this.Restaurant.Name = "Crazy大象";
  69. this.Restaurant.Address = "上海路8号";
  70. this.Restaurant.PhoneNumber = 66884725;
  71. }
  72. private void LoadDishMenu() {
  73. XmlDataService ds = new XmlDataService();
  74. var dishes = ds.GetAllDishes();
  75. this.DishMenu = new List<DishMenuItemViewModel>();
  76. foreach (var dish in dishes) {
  77. var item = new DishMenuItemViewModel();
  78. item.Dish = dish;
  79. this.DishMenu.Add(item);
  80. }
  81. }
  82. private void PlaceOrderCommandExecute() {
  83. var selectedDishes = this.DishMenu.Where(i => i.IsSelected == true).Select(i => i.Dish.Name).ToList();
  84. IOrderService orderService = new MockOrderService();
  85. orderService.PlaceOrder(selectedDishes);
  86. MessageBox.Show("Order Successfully!");
  87. }
  88. private void SelectMenuItemExecute() {
  89. this.Count = this.DishMenu.Count(i => i.IsSelected == true);
  90. }
  91. }
  92. }

View层

  1. <Window x:Class="CrazyElephant.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:CrazyElephant"
  7. mc:Ignorable="d"
  8. Title="{Binding Restaurant.Name,StringFormat=\{0\}-在线订餐}" Height="600" Width="1000">
  9. <Border BorderBrush="AntiqueWhite" BorderThickness="3" CornerRadius="6" Background="Orange">
  10. <Grid x:Name="Root" Margin="4">
  11. <Grid.RowDefinitions>
  12. <RowDefinition Height="Auto"/>
  13. <RowDefinition Height="*"/>
  14. <RowDefinition Height="Auto"/>
  15. </Grid.RowDefinitions>
  16. <Border BorderBrush="Orange" BorderThickness="1" CornerRadius="6" Padding="4">
  17. <StackPanel>
  18. <StackPanel Orientation="Horizontal">
  19. <StackPanel.Effect>
  20. <DropShadowEffect Color="LightGray"/>
  21. </StackPanel.Effect>
  22. <TextBlock Text="欢迎光临" FontSize="60" FontFamily="LiShu"/>
  23. <TextBlock Text="{Binding Restaurant.Name}" FontSize="60" FontFamily="Lishu"/>
  24. </StackPanel>
  25. <StackPanel Orientation="Horizontal">
  26. <TextBlock Text="小店地址:" FontSize="24" FontFamily="LiShu"/>
  27. <TextBlock Text="{Binding Restaurant.Address}" FontSize="24" FontFamily="LiShu"/>
  28. </StackPanel>
  29. <StackPanel Orientation="Horizontal">
  30. <TextBlock Text="订餐电话:" FontSize="24" FontFamily="LiShu"/>
  31. <TextBlock Text="{Binding Restaurant.PhoneNumber}" FontSize="24" FontFamily="LiShu"/>
  32. </StackPanel>
  33. </StackPanel>
  34. </Border>
  35. <DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" CanUserDeleteRows="False"
  36. CanUserAddRows="False" Margin="0.4" Grid.Row="1" FontSize="16" ItemsSource="{Binding DishMenu}">
  37. <DataGrid.Columns>
  38. <DataGridTextColumn Header="菜品" Binding="{Binding Dish.Name}" Width="120"/>
  39. <DataGridTextColumn Header="种类" Binding="{Binding Dish.Category}" Width="120"/>
  40. <DataGridTextColumn Header="点评" Binding="{Binding Dish.Comment}" Width="120"/>
  41. <DataGridTextColumn Header="推荐分数" Binding="{Binding Dish.Score}" Width="120"/>
  42. <DataGridTemplateColumn Header="选中" SortMemberPath="IsSelected" Width="120">
  43. <DataGridTemplateColumn.CellTemplate>
  44. <DataTemplate>
  45. <CheckBox IsChecked="{Binding Path=IsSelected,UpdateSourceTrigger=PropertyChanged}"
  46. VerticalAlignment="Center" HorizontalAlignment="Center"
  47. Command="{Binding Path=DataContext.SelectMenuItemCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}}"/>
  48. </DataTemplate>
  49. </DataGridTemplateColumn.CellTemplate>
  50. </DataGridTemplateColumn>
  51. </DataGrid.Columns>
  52. </DataGrid>
  53. <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2">
  54. <TextBlock Text="共计" VerticalAlignment="Center"/>
  55. <TextBox IsReadOnly="True" TextAlignment="Center" Width="120" Text="{Binding Count}" Margin="4.0"/>
  56. <Button Content="Order" Height="24" Width="120" Command="{Binding PlaceOrderCommand}"/>
  57. </StackPanel>
  58. </Grid>
  59. </Border>
  60. </Window>

示例代码

WPFMVVMSample.7z
CrazyElephant.7z