背景:
需求:实现从数据库读取级联表指定字段数据,并展示到前台界面。
VM层做业务逻辑层,每页最多获取2条数据。
View层只有数据表格,上一页与下一页按钮,且上一页与下一页在特定条件下不可用。
(转载请注明来源:cnblogs coder-fang)
解决方案结构如下:
-
-
- 项目结构:
- WPFTest:主要是界面显示数据(V层),ViewModel是wpftest的VM层,unittest做vm及数据的测试项目。
- 示例用的数据结构如图:
-
- 创建类库VM项目,为了简化,这里将M层与VM层放到了同一项目中,首先在VM中使用EF框架生成相关数据实体与context(EF自行查阅,这里不多介绍),即M层,项目目录如下:
- 为了使V层(展示层)与核心业务控制解耦,需要在VM层实现对业务的控制,建通用Command类,代码如下:
using 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,是显示层主要的数据提供者,与业务控制者,代码如下:
using 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+" "+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,界面代码如下:
<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,代码如下:
using 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进行开发还需要很多其它因素的参考,希望大家灵活运用。