在前三章中咱们完成了登陆窗口, 并掌握了使用Conductor来切换窗口, 但这些其实都是在为咱们的系统打基础.html
而本章中咱们就要开始开发系统的核心功能, 即图书管理功能了.git
经过本章, 咱们会接触到如下知识点:github
让咱们开始吧!数据库
有朋友说咱们的系统界面有点简陋, 有点辜负WPF的美名. 其实UI并非本系列文章主要关注的内容. 可是既然有朋友指出来, 那么这里就稍微美化一下UI, 这样看起来也赏心悦目一些.编程
WPF的UI库有不少, 这里咱们使用一个颇有名的开源UI库: Material Design In XAML.网络
WPF使用UI库是很简单的, 这里就不作过多说明了, 朋友们可直接看代码. 使用后本章最终效果以下:app
MVVM中第一个M即为Model的意思, 接下来咱们就为图书建立Model类, 作为图书信息的模型.ide
在工程中建立一个名为"Models"的文件夹,并在该文件夹下建立Book
类和BookType
枚举,分别表明图书类和图书类型枚举:学习
Book
类内容以下:测试
/// <summary> /// 图书 /// </summary> public class Book { /// <summary> /// 书名 /// </summary> public string Name { get; set; } /// <summary> /// 类型 /// </summary> public BookType Type { get; set; } /// <summary> /// 出版年月 /// </summary> public DateTime PublishDate { get; set; } /// <summary> /// 价格 /// </summary> public float Price { get; set; } /// <summary> /// 封面URL /// </summary> public string CoverUrl { get; set; } public Book(string name, BookType type, DateTime publishDate, float price, string coverUrl) { Name = name; Type = type; PublishDate = publishDate; Price = price; CoverUrl = coverUrl; } }
BookType
内容以下:
public enum BookType { /// <summary> /// 未定义 /// </summary> Undefined, /// <summary> /// 传记 /// </summary> Biography, /// <summary> /// 奇幻 /// </summary> Fantastic, /// <summary> /// 恐怖 /// </summary> Horror, /// <summary> /// 科幻 /// </summary> ScienceFiction, /// <summary> /// 悬疑 /// </summary> Mystery, /// <summary> /// 编程 /// </summary> Programming, }
两个文件内容都很简单, 无需作过多解释.
虽然咱们的简易系统并不使用数据库, 可是咱们仍然须要将图书信息的获取抽象为一个单独的服务, 这样未来若是要实现从数据库(或其它位置, 如网络)获取图书信息, 只须要提供相关实现便可.
建立一个名为"Services"的文件夹, 并建立IBookService
接口和BookService
实现类
IBookService
接口定义以下:
public interface IBookService { IEnumerable<Book> GetAllBooks(); }
如今只须要有一个方法: GetAllBooks
- 获取全部图书
BookService
类实现以下:
public class BookService : IBookService { private readonly List<Book> _bookStore; public BookService() { _bookStore = new List<Book> { new Book("阿米尔·汗:我行我素", BookType.Biography, DateTime.Parse("2017-6"), 52.8f, "https://img1.doubanio.com/view/subject/l/public/s29467958.jpg"), new Book("三体:“地球往事”三部曲之一", BookType.ScienceFiction, DateTime.Parse("2008-1"), 23f, "https://img1.doubanio.com/view/subject/l/public/s2768378.jpg"), new Book("三体Ⅱ:黑暗森林", BookType.ScienceFiction, DateTime.Parse("2008-5"), 32f, "https://img3.doubanio.com/view/subject/l/public/s3078482.jpg"), new Book("三体Ⅲ:死神永生", BookType.ScienceFiction, DateTime.Parse("2010-11"), 32f, "https://img9.doubanio.com/view/subject/l/public/s26012674.jpg"), new Book("肖申克的救赎", BookType.Mystery, DateTime.Parse("2006-7"), 26.9f, "https://img9.doubanio.com/view/subject/l/public/s4007145.jpg"), }; } public IEnumerable<Book> GetAllBooks() { return _bookStore; } }
在Bootstrapper
类中的ConfigureIoC
方法中, 注册服务:
protected override void ConfigureIoC(IStyletIoCBuilder builder) { // Configure the IoC container in here builder.Bind<IBookService>().To<BookService>(); }
这样咱们就能够将IBookService
注入到须要使用的类中了.
MVVM中, 咱们可将界面拆解成一个个的小组件, 而后将它们组合在一块儿造成一个复杂的界面. 这样的好处有不少:
总之, 就是将UI的部分进行解耦, 达到分而治之的目的.
在未接触MVMM以前, 也许我会将图书显示的UI代码直接放在IndexView
中, 而学习了MVVM以后, 咱们就会很天然的想到将每一个图书项目的显示作成一个组件, 而后在IndexView
中将全部图书组合成一个列表来显示.
因此, 接下来看一下是如何建立图书项目的.
在"Pages\Books"文件夹下, 建立"BookItems"文件夹, 并建立一个BookItemViewModel
:
public class BookItemViewModel : Screen { public Book Book { get; } public BookItemViewModel(Book book) { Book = book; } }
在同一文件夹内, 建立BookItemView
:
<UserControl ... d:DataContext="{d:DesignInstance bookItems:BookItemViewModel}" > <materialDesign:Card Background="WhiteSmoke"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Image Margin="0 10 0 0" Source="{Binding Book.CoverUrl}" Height="150" Stretch="Uniform" /> <DockPanel Grid.Row="1"> <TextBlock Margin="0 10 0 0" DockPanel.Dock="Top" FontWeight="Bold" Text="{Binding Book.Name}" HorizontalAlignment="Center"></TextBlock> <TextBlock Text="{Binding Book.Price, StringFormat='¥{0}'}" Margin="0 20 10 10" Foreground="Red" FontWeight="Bold" VerticalAlignment="Bottom" HorizontalAlignment="Right"></TextBlock> </DockPanel> </Grid> </materialDesign:Card> </UserControl>
BookItemViewModel
为设计时实例, 为XAML提供智能提示实际开发中, 可充分利用第一章中讲解的Hot Reload功能, 在运行时调整XAML.
完成后的工程结构是这样的:
有了图书项目组件, 咱们就能够来填充图书列表了.
咱们使用ListView来显示图书信息, WPF中的ListView是一个很是灵活的控件, 配合WPF强大的模板特性, 在展示集合数据时, 几乎能够实现任何效果.
改造IndexViewModel
以下:
public class IndexViewModel : Screen { private readonly IBookService _bookService; public ObservableCollection<BookItemViewModel> BookItems { get; set; } = new ObservableCollection<BookItemViewModel>(); public IndexViewModel(IBookService bookService) { _bookService = bookService; } protected override void OnViewLoaded() { var viewModels = _bookService.GetAllBooks() .Select(book => new BookItemViewModel(book)) ; BookItems = new ObservableCollection<BookItemViewModel>(viewModels); } }
ObservableCollection
类型的属性, 名为BookItems
. 使用ObservableCollection能够在图书增长或减小时自动发送通知IBookService
, 并存储为成员变量OnViewLoaded
方法中, 调用IBookService.GetAllBooks
而后转换成图书列表ViewModel改造IndexView
以下:
<UserControl ... > <ListView ItemsSource="{Binding BookItems}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListView.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <ContentControl s:View.Model="{Binding}"></ContentControl> </DataTemplate> </ListView.ItemTemplate> </ListView> </UserControl>
IndexViewModel
中BookItems
ContentControl
作为ListView.ItemTemplate的数据模板
ShellView
中的写法相似, 使用Stylet提供的s:View.Model
为ContentControl绑定一个ViewModel(这里便是BookItemViewModel), Stylet会自动为该ContentControl加载View(即BookItemView)能够看到, IndexView并不知道BookItemView的存在, 一切都是由后面的ViewModel关联在一块儿的, 这样咱们就实现了View之间的解耦.
最后运行程序, 确认运行正常.
本章的任务就完成了. 在本章中, 咱们建立了一个图书组件, 并使用ViewModel First来驱动各个UI部分.下一章中咱们会讲解Master-Detail这种经典的数据表现形式. 但愿朋友们能多多留言, 交流心得. 源码托管在GITHUB上.
Happy Coding~