【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~

相關文章
相關標籤/搜索