• 【WPF on .NET Core 3.0】 Stylet演示项目


    在前三章中我们完成了登录窗口, 并掌握了使用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中将所有图书组合成一个列表来显示.

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

    • 在"PagesBooks"文件夹下, 创建"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~

  • 相关阅读:
    会说话的TOM猫的原理是什么
    ios5 中文键盘高度变高覆盖现有ui问题的解决方案(获取键盘高度的方法)
    HJ2 计算某字母出现次数
    HJ1 字符串最后一个单词的长度
    windows 服务开发和windows install开发
    DICOM简介
    Active Directory 开发
    WF Workflow 状态机工作流 开发
    DICOM Query 和Retrieve 的方法和定义
    步步为营UML建模系列七、表图(Data model diagram)
  • 原文地址:https://www.cnblogs.com/waku/p/12231354.html
Copyright © 2020-2023  润新知