基本常识
开发/学习环境的准备
- 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派生自Dish
private 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>