基本常识
开发/学习环境的准备
- 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设计模式实现:
<?xml version="1.0" encoding="utf-8"?><CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"><CodeSnippet Format="1.0.0"><Header><Title>propn</Title><Shortcut>propn</Shortcut><Description>属性和支持字段的代码片段,属性改变通知</Description><Author>Albert</Author><SnippetTypes><SnippetType>Expansion</SnippetType></SnippetTypes></Header><Snippet><Declarations><Literal><ID>type</ID><ToolTip>属性类型</ToolTip><Default>int</Default></Literal><Literal><ID>property</ID><ToolTip>属性名</ToolTip><Default>MyProperty</Default></Literal><Literal><ID>field</ID><ToolTip>支持此属性的变量</ToolTip><Default>myVar</Default></Literal></Declarations><Code Language="csharp"><![CDATA[private $type$ $field$;public $type$ $property${get { return $field$;}set{$field$ = value;this.RaisePropertyChanged("$property$");}}$end$]]></Code></Snippet></CodeSnippet></CodeSnippets>
MVVM设计模式
MVVM = Model-View-ViewModel,技术主要集中在View和ViewModel之间。
为什么要使用MVVM模式
- 团队层面:统一思维方式和实现方法
- 架构层面:稳定,解耦,富有禅意
- 代码层面:可读,可测,可替换
Prisim包含MVVM,模块化,依赖注入
适用范围:企业级,不适于游戏开发。
什么是Model
- 现实世界中对象抽象结果
什么是View和ViewModel
- View = UI
- ViewModel = Model for View
- ViewModel与View的沟通:
- 传递数据—数据属性
- 传递操作—命令属性
案例讲解
初级案例
- NotificationObject与数据属性
- DelegateCommand与命令属性
- View与ViewModel的交互(技术难点)
View层:
<Window x:Class="WPFMVVM.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WPFMVVM"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Grid><Button x:Name="btn_Save" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="792" Height="32"/><TextBox x:Name="tb1" HorizontalAlignment="Left" Height="25" Margin="0,53,0,0" TextWrapping="Wrap" Text="{Binding Input1}" VerticalAlignment="Top" Width="792"/><TextBox x:Name="tb2" HorizontalAlignment="Left" Height="25" Margin="0,106,0,0" TextWrapping="Wrap" Text="{Binding Input2}" VerticalAlignment="Top" Width="792"/><TextBox x:Name="tb3" HorizontalAlignment="Left" Height="25" Margin="0,158,0,0" TextWrapping="Wrap" Text="{Binding Result}" VerticalAlignment="Top" Width="792"/><Button x:Name="btn_Add" Content="Add" HorizontalAlignment="Left" VerticalAlignment="Top" Width="792" Height="32" Margin="0,210,0,0" Command="{Binding AddCommand}"/></Grid></Window>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();this.DataContext = new MainWindowViewModel();//此句不可省略}}
(1)NotificationObjects与数据属性**
using System;using System.Collections.Generic;using System.ComponentModel;using System.Linq;using System.Text;using System.Threading.Tasks;namespace WPFMVVM.ViewModels {//ViewModel的基类class NotificationObjects:INotifyPropertyChanged {public event PropertyChangedEventHandler PropertyChanged;public void RaisePropertyChanged(string propertyName) {if (this.PropertyChanged != null) {this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));}}}}
(2)Commands与命令属性
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Input;namespace WPFMVVM.Commands {class DelegateCommand : ICommand {public Action<object> ExecuteAction { get; set; }public Func<object,bool> CanExecuteFunc { get; set; }public event EventHandler CanExecuteChanged;//当命令能够执行时,有机会通知一下命令的调用者/// <summary>/// 命令呼叫者,判断命令能不能执行/// </summary>/// <param name="parameter"></param>/// <returns></returns>public bool CanExecute(object parameter) {if (this.CanExecuteFunc == null) {return true;}return this.CanExecuteFunc(parameter);}/// <summary>/// 当命令执行的时候做什么事情/// </summary>/// <param name="parameter"></param>public void Execute(object parameter) {if (this.ExecuteAction == null) {return;}this.ExecuteAction(parameter); //这个命令把要执行的事情,委托给了ExecuteAction这个委托所指向的方法}}}
(3)建模MainWindowViewModel
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using WPFMVVM.Commands;namespace WPFMVVM.ViewModels {class MainWindowViewModel:NotificationObjects {//建模,需要三个数据属性,两个命令属性private double input1;public double Input1 {get { return input1; }set {input1 = value;this.RaisePropertyChanged("Input1");}}private double input2;public double Input2 {get { return input2; }set {input2 = value;this.RaisePropertyChanged("Input2");}}private double result;public double Result {get { return result; }set {result = value;this.RaisePropertyChanged("Result"); //这个字符串必须跟属性的值相同}}public DelegateCommand AddCommand { get; set; }private void Add(object parameter) {this.Result = this.Input1 + this.Input2;}public MainWindowViewModel() {this.AddCommand = new DelegateCommand();this.AddCommand.ExecuteAction = new Action<object>(this.Add);//ExecuteAction委托变量}}}
进阶案例
- 使用Microsoft Prism
- 餐馆点餐系统Demo
ViewModels层
using CrazyElephant.Models;using Microsoft.Practices.Prism.ViewModel;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CrazyElephant.ViewModels {class DishMenuItemViewModel:NotificationObject {public Dish Dish { get; set; } //ViewModel有一个方式 另一种方式是一个,Dish派生自NotificationObject//DishMenuItemViewModel派生自Dishprivate bool isSelected; //通知界面是否改变public bool IsSelected {get { return isSelected; }set {isSelected = value;this.RaisePropertyChanged("IsSelected");}}}}using CrazyElephant.Models;using CrazyElephant.Services;using Microsoft.Practices.Prism.Commands;using Microsoft.Practices.Prism.ViewModel;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;namespace CrazyElephant.ViewModels {class MainWindowViewModel:NotificationObject {public DelegateCommand PlaceOrderCommand { get; set; }public DelegateCommand SelectMenuItemCommand { get; set; }private int count;public int Count {get { return count; }set {count = value;this.RaisePropertyChanged("Count");}}private Restaurant restaurant;public Restaurant Restaurant {get { return restaurant; }set {restaurant = value;this.RaisePropertyChanged("Restaurant");}}private List<DishMenuItemViewModel> dishMenu;public List<DishMenuItemViewModel> DishMenu {get { return dishMenu; }set {dishMenu = value;this.RaisePropertyChanged("DishMenu");}}public MainWindowViewModel() {this.LoadRestaurant();this.LoadDishMenu();this.PlaceOrderCommand = new DelegateCommand(new Action(this.PlaceOrderCommandExecute));this.SelectMenuItemCommand = new DelegateCommand(new Action(this.SelectMenuItemExecute));}private void LoadRestaurant() {this.Restaurant = new Restaurant();this.Restaurant.Name = "Crazy大象";this.Restaurant.Address = "上海路8号";this.Restaurant.PhoneNumber = 66884725;}private void LoadDishMenu() {XmlDataService ds = new XmlDataService();var dishes = ds.GetAllDishes();this.DishMenu = new List<DishMenuItemViewModel>();foreach (var dish in dishes) {var item = new DishMenuItemViewModel();item.Dish = dish;this.DishMenu.Add(item);}}private void PlaceOrderCommandExecute() {var selectedDishes = this.DishMenu.Where(i => i.IsSelected == true).Select(i => i.Dish.Name).ToList();IOrderService orderService = new MockOrderService();orderService.PlaceOrder(selectedDishes);MessageBox.Show("Order Successfully!");}private void SelectMenuItemExecute() {this.Count = this.DishMenu.Count(i => i.IsSelected == true);}}}
View层
<Window x:Class="CrazyElephant.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:CrazyElephant"mc:Ignorable="d"Title="{Binding Restaurant.Name,StringFormat=\{0\}-在线订餐}" Height="600" Width="1000"><Border BorderBrush="AntiqueWhite" BorderThickness="3" CornerRadius="6" Background="Orange"><Grid x:Name="Root" Margin="4"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Border BorderBrush="Orange" BorderThickness="1" CornerRadius="6" Padding="4"><StackPanel><StackPanel Orientation="Horizontal"><StackPanel.Effect><DropShadowEffect Color="LightGray"/></StackPanel.Effect><TextBlock Text="欢迎光临" FontSize="60" FontFamily="LiShu"/><TextBlock Text="{Binding Restaurant.Name}" FontSize="60" FontFamily="Lishu"/></StackPanel><StackPanel Orientation="Horizontal"><TextBlock Text="小店地址:" FontSize="24" FontFamily="LiShu"/><TextBlock Text="{Binding Restaurant.Address}" FontSize="24" FontFamily="LiShu"/></StackPanel><StackPanel Orientation="Horizontal"><TextBlock Text="订餐电话:" FontSize="24" FontFamily="LiShu"/><TextBlock Text="{Binding Restaurant.PhoneNumber}" FontSize="24" FontFamily="LiShu"/></StackPanel></StackPanel></Border><DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" CanUserDeleteRows="False"CanUserAddRows="False" Margin="0.4" Grid.Row="1" FontSize="16" ItemsSource="{Binding DishMenu}"><DataGrid.Columns><DataGridTextColumn Header="菜品" Binding="{Binding Dish.Name}" Width="120"/><DataGridTextColumn Header="种类" Binding="{Binding Dish.Category}" Width="120"/><DataGridTextColumn Header="点评" Binding="{Binding Dish.Comment}" Width="120"/><DataGridTextColumn Header="推荐分数" Binding="{Binding Dish.Score}" Width="120"/><DataGridTemplateColumn Header="选中" SortMemberPath="IsSelected" Width="120"><DataGridTemplateColumn.CellTemplate><DataTemplate><CheckBox IsChecked="{Binding Path=IsSelected,UpdateSourceTrigger=PropertyChanged}"VerticalAlignment="Center" HorizontalAlignment="Center"Command="{Binding Path=DataContext.SelectMenuItemCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}}"/></DataTemplate></DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn></DataGrid.Columns></DataGrid><StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2"><TextBlock Text="共计" VerticalAlignment="Center"/><TextBox IsReadOnly="True" TextAlignment="Center" Width="120" Text="{Binding Count}" Margin="4.0"/><Button Content="Order" Height="24" Width="120" Command="{Binding PlaceOrderCommand}"/></StackPanel></Grid></Border></Window>
