WPF MVVM
MVVM=Model+View+ViewModel
- Model:现实世界中对象抽象的结果,也就是实体模型
- View:UI界面
- ViewModel:为UI界面服务的模型,可以理解为数据传输对象(DTO)
ViewModel和View的沟通有两个方面:数据和操作 - 传递数据–使用数据属性
- 传递操作–使用命令属性
很多人不理解MVVM和MVC的区别,我个人的理解是,MVC中的C可控范围更大,不仅可以控制View也能控制Model。而MVVM中,View是主动从ViewModel中获取数据,如果获取不到也不会导致程序崩溃,虽然VIewModel也可以去操作View,但是原则是View层主动获取数据,ViewModel是被动的提供数据。
案例1:
关注点:NotificationObject与数据属性
DelegateCommand与命令属性

命令的传递(单向)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class DelegateCommand : ICommand { public bool CanExecute(object parameter) { if (this.CanExecuteFunc == null) { return true; }
return this.CanExecuteFunc(parameter); }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { if (this.ExecuteAction == null) { return; } this.ExecuteAction(parameter); }
public Action<object> ExecuteAction { get; set; } public Func<object, bool> CanExecuteFunc { get; set; } }
|
ViewModels
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
|
该案例只有一个MainWindow,所以创建一个MainWindowViewModel类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class MainWindowViewModel : NotificationObject { 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; } public DelegateCommand SaveCommand { get; set; }
private void Add(object parameter) { this.Result = this.Input1 + this.Input2; }
private void Save(object parameter) { SaveFileDialog dlg = new SaveFileDialog(); dlg.ShowDialog(); }
public MainWindowViewModel() { this.AddCommand = new DelegateCommand(); this.AddCommand.ExecuteAction = new Action<object>(this.Add);
this.SaveCommand = new DelegateCommand(); this.SaveCommand.ExecuteAction = new Action<object>(this.Save); } }
|
主界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <Window x:Class="SimpleMvvmDemo.Client.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Menu> <MenuItem Header="_File"> <MenuItem Header="_Save" Command="{Binding SaveCommand}" /> </MenuItem> </Menu> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Slider x:Name="slider1" Grid.Row="0" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input1}" /> <Slider x:Name="slider2" Grid.Row="1" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input2}" /> <Slider x:Name="slider3" Grid.Row="2" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Result}" /> <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Command="{Binding AddCommand}"/> </Grid> </Grid> </Window>
|
由于Binding不指定Source默认使用了控件的DataContext,所以只需要在MainWindow中增加DataContext,子代控件便可以共享这个DataContext。
1 2 3 4 5 6 7 8
| public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); } }
|
案例2:

项目使用了Microsoft.Practices.Prism,需要提前引用
要显示单品和饭店信息,所以定义Dish和Restaurant两个类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Dish : NotificationObject { public string Name { get; set; } public string Category { get; set; } public string Comment { get; set; } public double Score { get; set; } } class Restaurant : NotificationObject { public string Name { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } }
|
具有获得菜单和点单服务,先定义相关接口,再实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface IDataService { List<Dish> GetAllDishes(); } class XmlDataService : IDataService { public List<Dish> GetAllDishes() { List<Dish> dishList = new List<Dish>(); string xmlFileName = System.IO.Path.Combine(Environment.CurrentDirectory, @"Data\Data.xml"); XDocument xDoc = XDocument.Load(xmlFileName); var dishes = xDoc.Descendants("Dish"); foreach (var d in dishes) { Dish dish = new Dish(); dish.Name = d.Element("Name").Value; dish.Category = d.Element("Category").Value; dish.Comment = d.Element("Comment").Value; dish.Score = double.Parse(d.Element("Score").Value); dishList.Add(dish); }
return dishList; } }
|
1 2 3 4 5 6 7 8 9 10 11
| interface IOrderService { void PlaceOrder(List<string> dishes); } class MockOrderService : IOrderService { public void PlaceOrder(List<string> dishes) { System.IO.File.WriteAllLines(@"C:\Users\cni23287938\Desktop\orders.txt", dishes.ToArray()); } }
|
因为菜单后面需要具有一个是否选择项,所以要新定义一个DishMenuItemViewModel类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class DishMenuItemViewModel : NotificationObject { public Dish Dish { get; set; }
private bool isSelected;
public bool IsSelected { get { return isSelected; } set { isSelected = value; this.RaisePropertyChanged("IsSelected"); } } }
|
MainWindowViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class MainWindowViewModel : NotificationObject { 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 DelegateCommand PlaceOrderCommand { get; set; } public DelegateCommand SelectMenuItemCommand { get; set; }
public MainWindowViewModel() { LoadRestaurant(); LoadDishMenu(); PlaceOrderCommand = new DelegateCommand(this.PlaceOrderCommandExecute); SelectMenuItemCommand = new DelegateCommand(this.SelectMenuItemExecute); }
private void LoadRestaurant() { this.Restaurant = new Restaurant(); this.Restaurant.Name = "Crazy"; this.restaurant.Address = "北京"; this.restaurant.PhoneNumber = "1"; }
private void LoadDishMenu() { IDataService ds = new XmlDataService(); var dishes = ds.GetAllDishes(); this.dishMenu = new List<DishMenuItemViewModel>(); foreach (var dish in dishes) { DishMenuItemViewModel 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("订餐成功"); } private void SelectMenuItemExecute() { Count = DishMenu.Count(i => i.IsSelected == true); } }
|
xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <Window x:Class="WpfApp8.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:WpfApp8" mc:Ignorable="d" Title="{Binding Restaurant.Name}" Height="600" Width="800" WindowStartupLocation="CenterScreen">
<Grid x:Name="Root" Margin="4"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel> <StackPanel Orientation="Horizontal" > <TextBlock Text="欢迎"/> <TextBlock Text="{Binding Restaurant.Name}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="电话"/> <TextBlock Text="{Binding Restaurant.PhoneNumber}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="地址"/> <TextBlock Text="{Binding Restaurant.Address}"/> </StackPanel> </StackPanel> <DataGrid AutoGenerateColumns="False" CanUserDeleteRows="False" CanUserAddRows="False" Grid.Row="1" 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}" Command="{Binding Path=DataContext.SelectMenuItemCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <StackPanel Grid.Row="2"> <TextBlock Text="共计:"/> <TextBox IsReadOnly="True" Text="{Binding Count}"/> <Button Content="Order" Command="{Binding PlaceOrderCommand}"/> </StackPanel> </Grid> </Window>
|