在前三章中我们完成了登录窗口, 并掌握了使用Conductor来切换窗口, 但这些其实都是在为我们的系统打基础.

而本章中我们就要开始开发系统的核心功能, 即图书管理功能了.

通过本章, 我们会接触到以下知识点:

  • 使用Stylet内置IoC
  • 使用ViewModel First解耦UI

让我们开始吧!

关于UI

有朋友说我们的系统界面有点简陋, 有点辜负WPF的美名. 其实UI并不是本系列文章主要关注的内容. 但是既然有朋友指出来, 那么这里就稍微美化一下UI, 这样看起来也赏心悦目一些.

WPF的UI库有很多, 这里我们使用一个很有名的开源UI库: Material Design In XAML.

WPF使用UI库是很简单的, 这里就不做过多说明了, 朋友们可直接看代码. 使用后本章最终效果如下:

Book Model

MVVM中第一个M即为Model的意思, 接下来我们就为图书创建Model类, 做为图书信息的模型.

  • 在工程中创建一个名为"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,
    }

    两个文件内容都很简单, 无需做过多解释.

Book服务

虽然我们的简易系统并不使用数据库, 但是我们仍然需要将图书信息的获取抽象为一个单独的服务, 这样将来如果要实现从数据库(或其它位置, 如网络)获取图书信息, 只需要提供相关实现即可.

  • 创建一个名为"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;
    }
    }
    • 在构造方法中, 创建一个List用来存储Book信息, 并使用代码初始化图书信息. 实际应用中, 这里一般是从数据库中取得数据.
    • GetAllBooks中直接返回list
  • Bootstrapper类中的ConfigureIoC方法中, 注册服务:

    protected override void ConfigureIoC(IStyletIoCBuilder builder)
    {
    // Configure the IoC container in here
    builder.Bind<IBookService>().To<BookService>();
    }

    这样我们就可以将IBookService注入到需要使用的类中了.

Book项目

MVVM中, 我们可将界面拆解成一个个的小组件, 然后将它们组合在一起形成一个复杂的界面. 这样的好处有很多:

  • 不同的开发者可负责不同的组件, 便于开发
  • 每个组件有自己View和ViewModel, 便于测试
  • 组件可以复用, 提高开发效率
  • 组件可替换, 便于扩展维护

总之, 就是将UI的部分进行解耦, 达到分而治之的目的.

在未接触MVMM之前, 也许我会将图书显示的UI代码直接放在IndexView中, 而学习了MVVM之后, 我们就会很自然的想到将每个图书项目的显示做成一个组件, 然后在IndexView中将所有图书组合成一个列表来显示.

所以, 接下来看一下是如何创建图书项目的.

  • 在"Pages\Books"文件夹下, 创建"BookItems"文件夹, 并创建一个BookItemViewModel:

    public class BookItemViewModel : Screen
    {
    public Book Book { get; } public BookItemViewModel(Book book)
    {
    Book = book;
    }
    }
    • 该ViewModel非常简单, 通过构造方法接收一个Book model, 然后通过只读属性将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>
    • 使用UserControl做为图书组件
    • 与Login类似, 使用d:DataContext指定BookItemViewModel为设计时实例, 为XAML提供智能提示
    • 使用MaterialDesign提供的Card控件来做为图书项目UI的容器
    • 然后分别显示了图书的封面, 书名和价格. 这里未用到Stylet的功能, 都是使用了WPF基本的绑定语法

    实际开发中, 可充分利用第一章中讲解的Hot Reload功能, 在运行时调整XAML.

    完成后的工程结构是这样的:

Book列表

有了图书项目组件, 我们就可以来填充图书列表了.

我们使用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>
    • 将ListView的ItemSource绑定到IndexViewModelBookItems
    • 使用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~

【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(4) - 图书列表界面的更多相关文章

  1. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(1)

    .NET Core 3.0已经发布了,除了一大堆令人激动的功能以外,也增加了对WPF的正式支持, 那么WPF在.NET Core 3.0下的开发体验如何呢? 本文利用了Stylet框架开发.NET C ...

  2. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(3) - 使用Conductor切换页面

    前两章中, 我们已经实现了这个图书管理系统的登录窗口, 并实施了完善的单元测试. 该是时候回过头来关注我们的主窗口了. 一个功能丰富的系统一般会有多个页面, 我们图书管理系统虽然是"简易&q ...

  3. 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(2) - 单元测试

    上一章中我们完成了一个简单的登录功能, 这一章主要演示如何对Stylet工程中的ViewModel进行单元测试. 回忆一下我们的登录逻辑,主要有以下4点: 当"用户名"或" ...

  4. Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台

    Windows Forms和WPF在Net Core 3.0框架下并不会支持跨平台 微软将WinForms和WPF带到.NET Core 3.0这一事实,相信大家都有所了解,这是否意味着它在Linux ...

  5. .Net Core .Net Core V1.0 创建MVC项目

    .Net Core V1.0 创建MVC项目 创建MVC项目有两种方式: 一.创建Web项目:(有太多没用的东西要去删太麻烦) 2.项目目录结构: 此种方法要注意的是,会创建好多个json文件,下面就 ...

  6. 用VSCode开发一个asp.net core 2.0+angular 5项目(4): Angular5全局错误处理

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...

  7. .Net Core 3.0开源可视化设计CMS内容管理系统建站系统

    简介 ZKEACMS,又名纸壳CMS,是可视化编辑设计的内容管理系统.基于.Net Core开发可跨平台运行,并拥有卓越的性能. 纸壳CMS基于插件式设计,功能丰富,易于扩展,可快速创建网站. 布局设 ...

  8. .Net大局观(2).NET Core 2.0 特性介绍和使用指南

    .NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,系统地介绍了.NET Core 2.0及生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路径.提纲来用. ...

  9. .Net Core 2.0 生态(2).NET Core 2.0 特性介绍和使用指南

    .NET Core 2.0发布日期:2017年8月14日 前言 这一篇会比较长,介绍了.NET Core 2.0新特性.工具支持及系统生态,现状及未来计划,可以作为一门技术的概述来读,也可以作为学习路 ...

随机推荐

  1. VScode快捷键(最全)

    按 Press 功能 Function Ctrl + Shift + P,F1 显示命令面板 Show Command Palette Ctrl + P 快速打开 Quick Open Ctrl + ...

  2. 【51.64%】【POJ 1330】Nearest Common Ancestors

    Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 26416 Accepted: 13641 Description A roote ...

  3. vue-learning:12-1- HTML5的<template>内容模板元素

    HTML5的<template>内容模板元素 HTML内容模板<template>元素将它其中的内容存储在页面文档中,以供后续使用,该内容的DOM结构在加载页面时会被解析器处理 ...

  4. API自动化测试指南

    我相信自动化技能已经成为高级测试工程师总体技能的标配.敏捷和持续测试破坏了传统的测试自动化实践,导致测试工程师重新考虑自动化的完成方式.当今的自动化工程师需要在GUI的下方深入到API级别完成软件质量 ...

  5. nginx负载均衡的几种模式

    nginx 的 upstream目前支持 4 种方式的分配 ).轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. ).weight 指定轮询几率,we ...

  6. 学习Java第六周

    1.内存结构 Java程序在运行时,需要在内存中的分配空间为提高运算效率,空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. 栈内存 ·用于存储局部变量,当数据使用完,所占 ...

  7. AlexNet,VGG,GoogleNet,ResNet

    AlexNet: VGGNet: 用3x3的小的卷积核代替大的卷积核,让网络只关注相邻的像素 3x3的感受野与7x7的感受野相同,但是需要更深的网络 这样使得参数更少 大多数内存占用在靠前的卷积层,大 ...

  8. 常见的java异常——java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path

    此异常是由于你的controller中有两个名字与内容相同的方法: 出现此异常时去检查你的controller中是否有重复的名字的方法:

  9. c# T4模板生成实体类(sqlserver)

    1.用vs新建tt文件. 2.tt文件保存就自动运行 3.tt文件代码如下,设置生成cs文件的命名空间和生成地址 <#@ template language="C#" hos ...

  10. 20191031-4 beta week 1/2 Scrum立会报告+燃尽图 02

    此作业要求参见 https://edu.cnblogs.com/campus/nenu/2019fall/homework/9912 git地址:https://e.coding.net/Eustia ...