WPF:MVVM解析探究
一 学习前提:
(1)Data Binding
(2)Dependency Property
(3)委托、事件、命令ICommand
上面三点内容,在学习MVVM之前要求简单了解并掌握使用。
MVVM介绍
MVC,Model - View - Controller的模式,页面和代码分离的写法,MVVM:Model - View - ViewModel,和WPF很好的进行结合,View负责界面,主要是写.xaml的文件,Model是一些实体类;ViewModel是联系两者的关键,并分离两者;
View需要什么,ViewModel就提供什么,如果将View理解为界面,Model和ViewModel以及Service等理解为后台的话,那么界面和后台是没有任何关系的,界面开发人员只要告诉后台人员需要哪些对象/属性,就可以进行开发了,二者之间的结合通过Binding操作进行绑定,解耦效果优于MVC,架构图如下:
几个重要的概念:
1、属性
1)数据绑定源:CLR对象、动态对象、ADO.NET 对象、XML对象、DependencyObject对象
2)命令属性:ICommand、
3) 集合对象绑定:ObservableCollection<T>;
2、NotificationObject类
3、ICommand接口
MVVM的难点和重点在于View以及MiewModel之间的绑定。
三 项目实战
效果如下:
按照上述架构图新建目录如下:
按照从底层到显示层的策略:
(1)Data
<?xml version="1.0" encoding="utf-8" ?> <Dishes> <Dish> <Name>土豆泥底披萨</Name> <Category>披萨</Category> <Comment>本店特色</Comment> <Score>4.5</Score> </Dish> <Dish> <Name>烤囊底披萨</Name> <Category>披萨</Category> <Comment>本店特色</Comment> <Score>5</Score> </Dish> <Dish> <Name>水果披萨</Name> <Category>披萨</Category> <Comment></Comment> <Score>4</Score> </Dish> <Dish> <Name>牛肉披萨</Name> <Category>披萨</Category> <Comment></Comment> <Score>5</Score> </Dish> </Dishes>
(2)Model
//定义Model,用于和xml文档中的节点属性匹配 class Dish { public string Name { get; set; } public string Category { get; set; } public string Comment { get; set; } public double Score { get; set; } }
class Restaurant { public string Name { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } }
(3)Service
接口:
interface IDataService { List<Dish> GetAllDishes(); }
interface IOrderService { void PlaceOrder(List<string> dishes); }
实现:
class XmlDataService:IDataService { public List<Dish> GetAllDishes() { //读取Dish集合,用于接收数据并返回值 List<Dish> dishList = new List<Dish>(); //读取XML文件路径 string xmlFileName = System.IO.Path.Combine(Environment.CurrentDirectory, @"Data\Data.xml"); //加载xml文件 XDocument xDoc = XDocument.Load(xmlFileName); //按照顺序返回<Dishes>集合下<Dish>标签里的所有内容 var dishes = xDoc.Descendants("Dish"); //将xml筛选的集合里的属性与Model对象绑定 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); //添加到List集合 dishList.Add(dish); } return dishList; } }
class MockOrderService:IOrderService { public void PlaceOrder(List<string> dishes) { System.IO.File.WriteAllLines(@"C:\order.txt", dishes.ToArray()); } }
至此,静态的代码编写完毕,无论是使用什么类型的框架,这部分东西大同小异。
(4)ViewModels
class DishMenuItemViewModel:NotificationObject { public Dish Dish { get; set; } //将IsSelected属性和Dish中的属性一起作为DishMenuItemViewModel里的属性 private bool isSelected; public bool IsSelected { get { return isSelected; } set { //RaisePropertyChanged方法,源于引入属性更改通知类 isSelected = value; this.RaisePropertyChanged("IsSeleted");//"IsSelected"属性值变化之后,自动通知使用该属性的方法,有点观察者模式的意思 } } }
class MainWindowViewModel : NotificationObject { public DelegateCommand PlaceOrderCommand { get; set; } public DelegateCommand SelectMenuItemCommand { get; set; } public DelegateCommand RemoveItemCommand { get; set; } private double count; //数据属性-Count public double Count { get { return count; } set { count = value; this.RaisePropertyChanged("Count"); } } private Restaurant restaurant; //数据属性-Restaurant public Restaurant Restaurant { get { return restaurant; } set { restaurant = value; this.RaisePropertyChanged("Restaurant"); } } private ObservableCollection<DishMenuItemViewModel> dishMenu; public ObservableCollection<DishMenuItemViewModel> DishMenu { get { return dishMenu; } set { dishMenu = value; this.RaisePropertyChanged("DishMenu"); } } public MainWindowViewModel() { this.LoadRestaurant(); this.LoadDishMenu(); //单向"命令属性"的加载过程,通过实例化委托的形式 this.PlaceOrderCommand = new DelegateCommand(); this.PlaceOrderCommand.ExecuteAction = new Action<object>(this.PlaceOrderCommandExecute); this.SelectMenuItemCommand = new DelegateCommand(); this.SelectMenuItemCommand.ExecuteAction = new Action<object>(this.SelectMenuItemExecute); this.RemoveItemCommand = new DelegateCommand(); this.RemoveItemCommand.ExecuteAction = new Action<object>(this.RemoveItem); } private void LoadRestaurant() { this.Restaurant = new Restaurant(); this.Restaurant.Name = "茶馆餐厅"; this.restaurant.Address = "北京市乐客灵境科技有限公司"; this.Restaurant.PhoneNumber = "12345678900"; } private void LoadDishMenu() { IDataService ds = new XmlDataService(); var dishes = ds.GetAllDishes(); //为数据属性赋值 this.DishMenu = new ObservableCollection<DishMenuItemViewModel>(); foreach (var dish in dishes) { DishMenuItemViewModel item = new DishMenuItemViewModel(); item.Dish = dish; item.IsSelected = false; this.DishMenu.Add(item); } } private void PlaceOrderCommandExecute(object parameter) { //lamad表达式的形式来选取所需要的数据 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(object parameter) { this.Count = this.DishMenu.Count(i => i.IsSelected == true); } private void RemoveItem(object parameter) { this.DishMenu.RemoveAt(0);//移除第一项 SelectMenuItemExecute(parameter);//更新Count值 } }
其中DelegateCommand就是继承ICommand而来,包括二个方法和一个事件:
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; } }
其实会发现,在ViewModel当中并没有很强的业务逻辑,业务逻辑更多的是放到Service当中的,在ViewModel当中所存放的内容,更多的会是一些属性,包括命令属性、数据属性,这些用于和View进行绑定,通过Binding,发现,后台的数据改变了,直接就会在前台页面上更新,这就是MVVM + WPF的魅力之一。同时也要知道,View和ViewModel之间的绑定,也是使用这个框架的难点之一。
(5)View
通过对控件的属性、样式进行设置.
通过Binding和后台数据进行绑定.
<Grid> <Border Grid.Row="0" BorderBrush="Orange" BorderThickness="3" CornerRadius="6" Background="Yellow"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <TextBlock FontSize="18" FontFamily="Bolt">订餐系统:</TextBlock> <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> <Grid Grid.Row="2" > <!--<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center"> <TextBlock>不能选取的菜名:</TextBlock> <CheckBox Name="_cbNo1">牛肉披萨</CheckBox> <CheckBox Name="_cbNo2">水果披萨</CheckBox> </StackPanel>--> <StackPanel HorizontalAlignment="Right" Orientation="Horizontal"> <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> </Grid> </Border> </Grid>
此时会发现,在View的.cs代码里,没有类似于onClick(),这样的方法,都通过绑定实现自动更新了。
(6)设置View的数据来源
public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); }
在View的.cs文件中,通过this.DataContext = new MainWindowViewModel();的方式,绑定该View的数据来自于哪个ViewModel。
ps:另外,主要参考文章http://blog.csdn.net/zzh92062...
我做了代码演示并进行一些扩展,主要在于把List<T>绑定更改为ObservableCollection<T>。就不会出现在List中移除一项,界面不自动更新的bug,各位可尝试,还可进行CanExecuteChanged的尝试。
至此,MVVM框架的简单实用,就通过这个例子实现了。 That's all.
相关推荐
<ListBox Name="sideMenu" SelectedIndex="{Binding MenuSelectedIndex}" ItemsSource="{Binding MenuList}