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

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

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

经过本章, 咱们会接触到如下知识点:github

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

让咱们开始吧!数据库

关于UI

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

WPF的UI库有不少, 这里咱们使用一个颇有名的开源UI库: Material Design In XAML.网络

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

Book Model

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,
    }

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

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~

相关文章
相关标签/搜索