WPF MVVM入门系列教程(六、ViewModel案例演示)
🧭 WPF MVVM入门系列教程
- 一、MVVM模式介绍
- 二、依赖属性
- 三、数据绑定
- 四、ViewModel
- 五、命令和用户输入
- 六、ViewModel案例演示
在前面的文章中,介绍了ViewModel的基础概念
本文会使用一些实例来进行ViewModel的演示
一个基础的数据展示示例
假设我们要在界面上对一本书籍的详细信息进行展示。
首先我们定义一下View
MainWindow.xaml
界面上我们定义了两列,左边一列用于展示书籍封面,右边一列用于展示详细信息
1 <Window x:Class="_1_ViewModelStartup.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:_1_ViewModelStartup"7 mc:Ignorable="d"8 Title="MainWindow" Height="500" Width="900" WindowStartupLocation="CenterScreen">9 <Grid> 10 <Grid.ColumnDefinitions> 11 <ColumnDefinition/> 12 <ColumnDefinition/> 13 </Grid.ColumnDefinitions> 14 15 <Image Source="{Binding Book.CoverImageUrl}" Stretch="Uniform" Margin="20"></Image> 16 17 <StackPanel Grid.Column="1"> 18 <TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Book.Title}" FontSize="25" FontWeight="Bold" Margin="10"></TextBlock> 19 <TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Book.Descrption}" FontSize="20" Foreground="Silver" Margin="10"></TextBlock> 20 <Separator Height="1" Foreground="Silver" Margin="10"></Separator> 21 <StackPanel Orientation="Horizontal" Margin="10"> 22 <Label Content="作者:"></Label> 23 <Label Content="{Binding Book.Author}" Foreground="Silver" ></Label> 24 </StackPanel> 25 <StackPanel Orientation="Horizontal" Margin="10"> 26 <Label Content="出版社:"></Label> 27 <Label Content="{Binding Book.Publish}" Foreground="Silver" ></Label> 28 </StackPanel> 29 <StackPanel Orientation="Horizontal" Margin="10"> 30 <Label Content="价格 ¥: " Foreground="Red" FontWeight="Bold"></Label> 31 <Label Content="{Binding Book.Price}" Foreground="Silver" ></Label> 32 </StackPanel> 33 <StackPanel Orientation="Horizontal" Margin="10"> 34 <Label Content="出版日期:"></Label> 35 <Label Content="{Binding Book.Date}" Foreground="Silver" ></Label> 36 </StackPanel> 37 </StackPanel> 38 </Grid> 39 40 </Window>
然后我们定义一下Model
Book.cs
1 public class Book : INotifyPropertyChanged2 {3 private string title;4 5 public string Title6 {7 get => title;8 set9 { 10 title = value; 11 RaiseChanged(); 12 } 13 } 14 15 private string author; 16 public string Author 17 { 18 get => author; 19 set 20 { 21 author = value; 22 RaiseChanged(); 23 } 24 } 25 26 private string publish; 27 28 public string Publish 29 { 30 get => publish; 31 set 32 { 33 publish = value; 34 RaiseChanged(); 35 } 36 } 37 38 private string price; 39 40 public string Price 41 { 42 get => price; 43 set 44 { 45 price = value; 46 RaiseChanged(); 47 } 48 } 49 50 private string date; 51 public string Date 52 { 53 get => date; 54 set 55 { 56 date = value; 57 RaiseChanged(); 58 } 59 } 60 61 private string description; 62 63 public string Descrption 64 { 65 get => description; 66 set 67 { 68 description = value; 69 RaiseChanged(); 70 } 71 } 72 73 74 private string coverImageUrl; 75 76 public string CoverImageUrl 77 { 78 get => coverImageUrl; 79 set 80 { 81 coverImageUrl = value; 82 RaiseChanged(); 83 } 84 } 85 86 87 public event PropertyChangedEventHandler PropertyChanged; 88 89 90 public void RaiseChanged([CallerMemberName] string memberName = "") 91 { 92 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName)); 93 } 94 }
定义ViewModel
MainWindowViewModel.cs
在ViewModel
里,定义一个Book
对象,并进行数据初始化。
而View
上的元素分别绑定到了该对象的各个属性。
1 public class MainWindowViewModel : INotifyPropertyChanged2 {3 private Book book;4 5 public Book Book 6 {7 get => book;8 set9 { 10 book = value; 11 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Book")); 12 } 13 } 14 15 public MainWindowViewModel() 16 { 17 LoadBook(); 18 } 19 20 public event PropertyChangedEventHandler PropertyChanged; 21 22 private void LoadBook() 23 { 24 Book = new Book(); 25 Book.CoverImageUrl = "http://img3m1.ddimg.cn/16/5/29681701-1_w_1709623057.jpg"; 26 Book.Title = "小学生C++创意编程(视频教学版)"; 27 Book.Descrption = "本书让入门C++变得轻松易懂,逐步入学。学习一步一个台阶,让孩子不会被其中的难度而吓退。"; 28 Book.Author = "刘凤飞"; 29 Book.Publish = "清华大学出版社"; 30 Book.Date = "2024年01月 "; 31 Book.Price = "74.00"; 32 } 33 }
设置DataContext
1 public partial class MainWindow : TianXiaTech.BlurWindow 2 { 3 public MainWindow() 4 { 5 InitializeComponent(); 6 7 this.DataContext = new MainWindowViewModel(); 8 } 9 }
运行效果如下:
带列表功能的示例
接下来我们将上面的示例进行升级,增加列表功能,并支持列表排序
首先我们升级一下界面,将书籍封面,移动到右上角,左侧增加一个ListBox。
其中ListBox使用数据模板
定义了数据呈现方式。
1 <Window x:Class="_2_ShowBookList.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:_2_ShowBookList"7 mc:Ignorable="d"8 Title="MainWindow" Height="450" Width="800">9 <Grid> 10 <Grid.ColumnDefinitions> 11 <ColumnDefinition Width="300"/> 12 <ColumnDefinition/> 13 </Grid.ColumnDefinitions> 14 15 <Grid> 16 <Grid.RowDefinitions> 17 <RowDefinition/> 18 <RowDefinition Height="45"/> 19 </Grid.RowDefinitions> 20 21 <ListBox ItemsSource="{Binding BookList}" SelectedItem="{Binding Book}" Margin="5"> 22 <ListBox.ItemTemplate> 23 <DataTemplate> 24 <Grid> 25 <Grid.RowDefinitions> 26 <RowDefinition/> 27 <RowDefinition/> 28 </Grid.RowDefinitions> 29 30 <!--使用数据模板,让书名支持换行--> 31 <TextBlock Text="{Binding Title}" FontWeight="Bold" TextWrapping="WrapWithOverflow" Width="260"></TextBlock> 32 <Grid Grid.Row="1" Margin="0,5,0,0"> 33 <Grid.ColumnDefinitions> 34 <ColumnDefinition/> 35 <ColumnDefinition/> 36 </Grid.ColumnDefinitions> 37 38 <Label Content="{Binding Author}"></Label> 39 <TextBlock Text="{Binding Price,StringFormat={}{0}元}" HorizontalAlignment="Left" Grid.Column="1"></TextBlock> 40 </Grid> 41 </Grid> 42 </DataTemplate> 43 </ListBox.ItemTemplate> 44 </ListBox> 45 46 <DockPanel LastChildFill="False" Grid.Row="1"> 47 <Button Content="价格升序" VerticalAlignment="Center" Margin="5,0,0,0" Command="{Binding OrderByPriceAscCommand}"></Button> 48 <Button Content="价格降序" VerticalAlignment="Center" Margin="5,0,0,0" Command="{Binding OrderByPriceDescCommand}"></Button> 49 </DockPanel> 50 </Grid> 51 52 <StackPanel Grid.Column="1"> 53 <Image Source="{Binding Book.CoverImageUrl}" Stretch="Uniform" Margin="20" Height="150"></Image> 54 55 <TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Book.Title}" FontWeight="Bold" Margin="5"></TextBlock> 56 <TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Book.Descrption}" Foreground="Silver" Margin="5"></TextBlock> 57 <Separator Height="1" Foreground="Silver" Margin="2"></Separator> 58 <StackPanel Orientation="Horizontal" Margin="5"> 59 <Label Content="作者:"></Label> 60 <Label Content="{Binding Book.Author}" Foreground="Silver" ></Label> 61 </StackPanel> 62 <StackPanel Orientation="Horizontal" Margin="5"> 63 <Label Content="出版社:"></Label> 64 <Label Content="{Binding Book.Publish}" Foreground="Silver" ></Label> 65 </StackPanel> 66 <StackPanel Orientation="Horizontal" Margin="5"> 67 <Label Content="价格 ¥: " Foreground="Red" FontWeight="Bold"></Label> 68 <Label Content="{Binding Book.Price}" Foreground="Silver" ></Label> 69 </StackPanel> 70 <StackPanel Orientation="Horizontal" Margin="5"> 71 <Label Content="出版日期:"></Label> 72 <Label Content="{Binding Book.Date}" Foreground="Silver" ></Label> 73 </StackPanel> 74 </StackPanel> 75 </Grid> 76 77 </Window>
此外,我们还要注意一下界面绑定:
ListBox
的数据源绑定到BookList
集合,SelectedItem
绑定到Book
对象
1 ListBox ItemsSource="{Binding BookList}" SelectedItem="{Binding Book}"
另外还增加了排序按钮
,分别绑定到两个不同的Command
1 <Button Content="价格升序" VerticalAlignment="Center" Margin="5,0,0,0" Command="{Binding OrderByPriceAscCommand}"></Button> 2 <Button Content="价格降序" VerticalAlignment="Center" Margin="5,0,0,0" Command="{Binding OrderByPriceDescCommand}"></Button>
然后我们在前面的基础上升级一下ViewModel
1 public class MainWindowViewModel : INotifyPropertyChanged2 {3 private Book book;4 5 public Book Book6 {7 get => book;8 set9 { 10 book = value; 11 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Book")); 12 } 13 } 14 15 private ObservableCollection<Book> bookList = new ObservableCollection<Book>(); 16 17 public ObservableCollection<Book> BookList 18 { 19 get => bookList; 20 set 21 { 22 bookList = value; 23 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BookList")); 24 } 25 } 26 27 public ICommand OrderByPriceAscCommand { get; private set; } 28 29 public ICommand OrderByPriceDescCommand { get; private set; } 30 31 32 public MainWindowViewModel() 33 { 34 OrderByPriceAscCommand = new RelayCommand(OrderByPriceAsc); 35 OrderByPriceDescCommand = new RelayCommand(OrderByPriceDesc); 36 37 LoadBook(); 38 } 39 40 private void OrderByPriceDesc() 41 { 42 BookList = new ObservableCollection<Book>(BookList.OrderByDescending(x => x.Price)); 43 } 44 45 private void OrderByPriceAsc() 46 { 47 BookList = new ObservableCollection<Book>(BookList.OrderBy(x => x.Price)); 48 } 49 50 public event PropertyChangedEventHandler PropertyChanged; 51 52 private void LoadBook() 53 { 54 Book book = new Book(); 55 book.CoverImageUrl = "http://img3m1.ddimg.cn/16/5/29681701-1_w_1709623057.jpg"; 56 book.Title = "小学生C++创意编程(视频教学版)"; 57 book.Descrption = "本书让入门C++变得轻松易懂,逐步入学。学习一步一个台阶,让孩子不会被其中的难度而吓退。"; 58 book.Author = "刘凤飞"; 59 book.Publish = "清华大学出版社"; 60 book.Date = "2024年01月 "; 61 book.Price = 74.00f; 62 63 Book book2 = new Book(); 64 book2.CoverImageUrl = "http://img3m4.ddimg.cn/64/13/29798074-1_u_1731275892.jpg"; 65 book2.Title = "漫画趣读西游记(7-14岁)和大人一起读四大名著儿童文学,十万个为什么中小学课外阅读快乐读书吧"; 66 book2.Descrption = "拼音标注、无障阅读、名著导读、有声伴读、Q版漫画、全彩印刷,鲜活的人物形象,激发兴趣,提升孩子的学习力!"; 67 book2.Author = "陈春燕"; 68 book2.Publish = "四川美术出版社"; 69 book2.Date = "2024年09月"; 70 book2.Price = 4.89f; 71 72 BookList.Add(book); 73 BookList.Add(book2); 74 } 75 }
运行效果如下:
带新增功能的示例
我们在前面列表的基础上,再进行升级,支持从界面新增书籍。
1 <Grid>2 <Grid.RowDefinitions>3 <RowDefinition/>4 <RowDefinition Height="35"/>5 </Grid.RowDefinitions>6 7 <Grid>8 <Grid.ColumnDefinitions>9 <ColumnDefinition/> 10 <ColumnDefinition/> 11 </Grid.ColumnDefinitions> 12 13 <Image Stretch="Uniform" Margin="0,0,0,30" Source="{Binding NewBook.CoverImageUrl}"></Image> 14 <Button Content="浏览封面" HorizontalAlignment="Center" VerticalAlignment="Bottom" Command="{Binding BrowseCoverCommand}"></Button> 15 16 <StackPanel Grid.Column="1"> 17 <StackPanel Orientation="Horizontal" Margin="10"> 18 <Label Content="书名:"></Label> 19 <TextBox Text="{Binding NewBook.Title}" Foreground="Silver" Width="200" ></TextBox> 20 </StackPanel> 21 <StackPanel Orientation="Horizontal" Margin="10"> 22 <Label Content="描述:"></Label> 23 <TextBox Text="{Binding NewBook.Descrption}" Foreground="Silver" Width="200" ></TextBox> 24 </StackPanel> 25 <StackPanel Orientation="Horizontal" Margin="10"> 26 <Label Content="作者:"></Label> 27 <TextBox Text="{Binding NewBook.Author}" Foreground="Silver" Width="200" ></TextBox> 28 </StackPanel> 29 <StackPanel Orientation="Horizontal" Margin="10"> 30 <Label Content="出版社:"></Label> 31 <TextBox Text="{Binding NewBook.Publish}" Foreground="Silver" Width="200"></TextBox> 32 </StackPanel> 33 <StackPanel Orientation="Horizontal" Margin="10"> 34 <Label Content="价格 ¥: " Foreground="Red" FontWeight="Bold"></Label> 35 <TextBox Text="{Binding NewBook.Price}" Foreground="Silver" Width="200"></TextBox> 36 </StackPanel> 37 <StackPanel Orientation="Horizontal" Margin="10"> 38 <Label Content="出版日期:"></Label> 39 <TextBox Text="{Binding NewBook.Date}" Foreground="Silver" Width="200"></TextBox> 40 </StackPanel> 41 </StackPanel> 42 </Grid> 43 44 <Button Content="新增" Width="88" Height="28" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Command="{Binding AddBookCommand}"/> 45 </Grid>
需要注意的是,这里我们将增加书籍的信息绑定到了一个NewBook对象。
此外,我们还定义了BrowseCoverCommand和AddBookCommand,分别用于浏览书籍封面和新增加书籍
然后我们更新ViewModel
这里为了防止混淆,将前面示例部分的代码移除了,完整的代码可以参考文末的示例代码
1 public class MainWindowViewModel2 {3 ... 4 5 private Book newBook = new Book();6 7 public Book NewBook8 {9 get => newBook; 10 set 11 { 12 newBook = value; 13 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NewBook")); 14 } 15 } 16 17 public ICommand BrowseCoverCommand { get; private set; } 18 19 public ICommand AddBookCommand { get; private set; } 20 21 22 public event PropertyChangedEventHandler? PropertyChanged; 23 24 public MainWindowViewModel() 25 { 26 BrowseCoverCommand = new RelayCommand(BrowseCover); 27 AddBookCommand = new RelayCommand(AddBook); 28 29 LoadDemoData(); 30 } 31 32 private void LoadDemoData() 33 { 34 Book book = new Book(); 35 book.CoverImageUrl = "http://img3m1.ddimg.cn/16/5/29681701-1_w_1709623057.jpg"; 36 book.Title = "小学生C++创意编程(视频教学版)"; 37 book.Descrption = "本书让入门C++变得轻松易懂,逐步入学。学习一步一个台阶,让孩子不会被其中的难度而吓退。"; 38 book.Author = "刘凤飞"; 39 book.Publish = "清华大学出版社"; 40 book.Date = "2024年01月 "; 41 book.Price = "74.00"; 42 43 Book book2 = new Book(); 44 book2.CoverImageUrl = "http://img3m4.ddimg.cn/64/13/29798074-1_u_1731275892.jpg"; 45 book2.Title = "漫画趣读西游记(7-14岁)和大人一起读四大名著儿童文学,十万个为什么中小学课外阅读快乐读书吧"; 46 book2.Descrption = "拼音标注、无障阅读、名著导读、有声伴读、Q版漫画、全彩印刷,鲜活的人物形象,激发兴趣,提升孩子的学习力!"; 47 book2.Author = "陈春燕"; 48 book2.Publish = "四川美术出版社"; 49 book2.Date = "2024年09月"; 50 book2.Price = "4.89"; 51 52 BookList.Add(book); 53 BookList.Add(book2); 54 } 55 56 private void AddBook() 57 { 58 var tempBook = new Book(); 59 tempBook.Title = NewBook.Title; 60 tempBook.Descrption = NewBook.Descrption; 61 tempBook.Author = NewBook.Author; 62 tempBook.Publish = NewBook.Publish; 63 tempBook.Price = NewBook.Price; 64 tempBook.Date = NewBook.Date; 65 tempBook.CoverImageUrl = NewBook.CoverImageUrl; 66 67 //将NewBook添加到列表中 68 BookList.Add(tempBook); 69 70 //重置NewBook 71 NewBook.Title = ""; 72 NewBook.Descrption = ""; 73 NewBook.Author = ""; 74 NewBook.Publish = ""; 75 NewBook.Price = ""; 76 NewBook.Date = ""; 77 NewBook.CoverImageUrl = null; 78 } 79 80 private void BrowseCover() 81 { 82 Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog(); 83 openFileDialog.Filter = "图片文件|*.jpg;*.bmp;*.png"; 84 85 if (openFileDialog.ShowDialog() == true) 86 { 87 NewBook.CoverImageUrl = openFileDialog.FileName; 88 } 89 } 90 91 ... 92 }
运行效果如下:
示例代码
https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/6_ViewModelDemo