🧭 WPF MVVM入门系列教程
在前面的文章中,介绍了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 : INotifyPropertyChanged
2 {
3 private string title;
4
5 public string Title
6 {
7 get => title;
8 set
9 {
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 : INotifyPropertyChanged
2 {
3 private Book book;
4
5 public Book Book
6 {
7 get => book;
8 set
9 {
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 : INotifyPropertyChanged
2 {
3 private Book book;
4
5 public Book Book
6 {
7 get => book;
8 set
9 {
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 MainWindowViewModel
2 {
3 ...
4
5 private Book newBook = new Book();
6
7 public Book NewBook
8 {
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