背景:
需求:實現從數據庫讀取級聯表指定字段數據,並展示到前台界面。
VM層做業務邏輯層,每頁最多獲取2條數據。
View層只有數據表格,上一頁與下一頁按鈕,且上一頁與下一頁在特定條件下不可用。
(轉載請注明來源:cnblogs coder-fang)
解決方案結構如下:
-
-
- 項目結構:

- WPFTest:主要是界面顯示數據(V層),ViewModel是wpftest的VM層,unittest做vm及數據的測試項目。
- 示例用的數據結構如圖:

- 項目結構:
-
- 創建類庫VM項目,為了簡化,這里將M層與VM層放到了同一項目中,首先在VM中使用EF框架生成相關數據實體與context(EF自行查閱,這里不多介紹),即M層,項目目錄如下:
- 為了使V層(展示層)與核心業務控制解耦,需要在VM層實現對業務的控制,建通用Command類,代碼如下:
View Codeusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Input; namespace ViewModels { public class Command :ICommand { private Action methodToExecute = null; private Func<bool> methodCanExecute = null; public Command(Action methodToExecute, Func<bool> methodCanExecute) { this.methodToExecute = methodToExecute; this.methodCanExecute = methodCanExecute; } public void Execute(object parameter) { this.methodToExecute(); } public bool CanExecute(object parameter) { if (this.methodCanExecute == null) { return true; } else { return this.methodCanExecute(); } } public event EventHandler CanExecuteChanged; public void RaseCanExecuteChangedEvent() { if (this.CanExecuteChanged != null) { this.CanExecuteChanged(this, EventArgs.Empty); } } } }
這里的command主要參數為命令調用的函數委托,是否可執行的函數委托
- 創建DatagridVM,是顯示層主要的數據提供者,與業務控制者,代碼如下:
View Codeusing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Text; using System.Windows.Input; using ViewModels; namespace ViewModels { public class GridMember { public string Name { get; set; } public string Role { get; set; } public string Depart { get; set; } } public class DatagridVM : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private List<GridMember> _griddata; public Command preCmd {get;private set;} public Command nextCmd { get; private set; } public Action<String> errCallback { get; set; } private int _curpage = 1; private int _total = 0; public void getData() { using (dbEntities ctx = new dbEntities()) { try { Total = ctx.user.Count(); var users = (from c in ctx.user orderby c.id select new GridMember{ Name = c.username, Role = c.Role1.rolename, Depart = c.deprtment.departname }).Skip((CurPage - 1) * 2).Take(2); foreach (var item in users) { Console.WriteLine(item.Name); } this.GridData = users.ToList(); } catch (Exception e) { if (errCallback != null) errCallback(e.Message+"\r\n"+e.StackTrace); } } } public List<GridMember> GridData { get { return _griddata; } set { _griddata = value; OnPropertyChanged("GridData"); } } public int CurPage { get { return _curpage; } set { _curpage = value; OnPropertyChanged("CurPage"); preCmd.RaseCanExecuteChangedEvent(); nextCmd.RaseCanExecuteChangedEvent(); } } public int Total { get { return _total; } set { _total = value; OnPropertyChanged("Total"); } } public DatagridVM() { preCmd = new Command(() => { CurPage--; getData(); }, () => { return (bool)(CurPage > 1); }); nextCmd = new Command(() => { CurPage++; getData(); }, () => { return (bool)(CurPage * 2 < Total); }); getData(); } protected void OnPropertyChanged(string name) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(name)); } } }
注:其中GridMember為VM需要從數據庫中查詢的(多表聯合后)數據字段,也是顯示層要顯示的字段,且創建了兩個命令,用來實現上一頁與下一頁的業務邏輯,在CurPage改變時,需要發出一個事件,即相關命令更新自己的可執行狀態。
- 至此,VM層已完成,下面創建顯示層,創建簡單的WPF窗口項目:

-
此項目需引用ViewModels,創建新窗口,Datagrid.xaml,界面代碼如下:
View Code<Window x:Class="WPFTest.Datagrid" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ViewModels;assembly=ViewModels" Title="Datagrid" Height="311.417" Width="528.358"> <Window.Resources> <local:DatagridVM x:Key="VM"/> </Window.Resources> <Grid Name="Grid" DataContext="{StaticResource VM}"> <Grid.RowDefinitions> <RowDefinition Height="auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Margin="10" Orientation="Horizontal" > <Button Name="Pre" Command="{Binding Path=preCmd}" >上一頁</Button> <Button Name="Next" Command="{Binding Path=nextCmd}" >下一頁</Button> </StackPanel> <DataGrid Name="usersGrid" Grid.Row="1" ItemsSource="{Binding Path=GridData}"></DataGrid> </Grid> </Window>
注意這里的兩個button,並沒有實現click,反而使用命令綁定,自動調用了VM的執行函數,自動更新可執行狀態,這就解耦了界面與業務。
- 整個界面已完成,是的,UI只需要編輯這個文件即可,已經將業務與UI分離了出來。
-
運行效果:首頁:
最后一頁:
下面進行對VM層的單元測試
-
創建C#的單元測試項目,並引用viewmodel:

-
創建DataGridVMTest,代碼如下:
View Codeusing System; using Microsoft.VisualStudio.TestTools.UnitTesting; using ViewModels; using System.Collections.Generic; using System.Linq; namespace UnitTest { [TestClass] public class DataGridVMTest { [TestMethod] public void testFunc() { DatagridVM vm = new DatagridVM(); vm.errCallback = (e) => { Console.WriteLine("出現異常:"+e); }; Assert.AreEqual(vm.CurPage, 1); Assert.AreEqual(vm.Total, 7); Assert.IsFalse(vm.preCmd.CanExecute(null)); Assert.IsTrue(vm.nextCmd.CanExecute(null)); vm.CurPage = 3; Assert.IsTrue(vm.preCmd.CanExecute(null)); Assert.IsTrue(vm.nextCmd.CanExecute(null)); vm.nextCmd.Execute(null); Assert.IsTrue(vm.preCmd.CanExecute(null)); Assert.IsFalse(vm.nextCmd.CanExecute(null)); Assert.AreEqual(vm.GridData.Count, 1); } } }
注:數據庫中有7條記錄,每頁顯示2條,所以在testFunc中,分別測試不同頁碼時,preCmd與nextCmd的可執行狀態,且在最后一頁時測試獲取數據的Count
-
執行結果:

本次實踐已完成。
總結:MVVM前期需要花費一定的工作量,但帶來的效果是顯而易見的,當然,是否使用MVVM進行開發還需要很多其它因素的參考,希望大家靈活運用。
