【WPF on .NET Core 3.0】 Stylet演示專案 - 簡易圖書管理系統(4) - 圖書列表介面
在前三章中我們完成了登入視窗, 並掌握了使用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繫結到
IndexViewModel
中BookItems
- 使用
ContentControl
做為ListView.ItemTemplate的資料模板- 同
ShellView
中的寫法類似, 使用Stylet提供的s:View.Model
為ContentControl繫結一個ViewModel(這裡即是BookItemViewModel), Stylet會自動為該ContentControl載入View(即BookItemView)
- 同
可以看到, IndexView並不知道BookItemView的存在, 一切都是由後面的ViewModel關聯在一起的, 這樣我們就實現了View之間的解耦.
- 將ListView的ItemSource繫結到
最後執行程式, 確認執行正常.
本章的任務就完成了. 在本章中, 我們建立了一個圖書元件, 並使用ViewModel First來驅動各個UI部分.下一章中我們會講解Master-Detail這種經典的資料表現形式. 希望朋友們能多多留言, 交流心得. 原始碼託管在GITHUB上.
Happy Codi