C# 实践之 基于WPF的mvvm模型,使UI独立,逻辑可测

背景:

  需求:实现从数据库读取级联表指定字段数据,并展示到前台界面。

  VM层做业务逻辑层,每页最多获取2条数据。

  View层只有数据表格,上一页与下一页按钮,且上一页与下一页在特定条件下不可用。

 (转载请注明来源:cnblogs coder-fang)

                      解决方案结构如下:

      • 项目结构:       
      • WPFTest:主要是界面显示数据(V层),ViewModel是wpftest的VM层,unittest做vm及数据的测试项目。
      • 示例用的数据结构如图:

       

 

  1. 创建类库VM项目,为了简化,这里将M层与VM层放到了同一项目中,首先在VM中使用EF框架生成相关数据实体与context(EF自行查阅,这里不多介绍),即M层,项目目录如下:

     

  2. 为了使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);
                }
            }
        }
    }
    View Code

    这里的command主要参数为命令调用的函数委托,是否可执行的函数委托

  3. 创建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+"\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));
            }
    
           
        }
    }
    View Code

    注:其中GridMember为VM需要从数据库中查询的(多表联合后)数据字段,也是显示层要显示的字段,且创建了两个命令,用来实现上一页与下一页的业务逻辑,在CurPage改变时,需要发出一个事件,即相关命令更新自己的可执行状态。

  4. 至此,VM层已完成,下面创建显示层,创建简单的WPF窗口项目:                                                       
  5. 此项目需引用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>
    View Code

    注意这里的两个button,并没有实现click,反而使用命令绑定,自动调用了VM的执行函数,自动更新可执行状态,这就解耦了界面与业务。

  6. 整个界面已完成,是的,UI只需要编辑这个文件即可,已经将业务与UI分离了出来。

     

  7.  

    运行效果:首页:最后一页:

     

     下面进行对VM层的单元测试

  8.  

     创建C#的单元测试项目,并引用viewmodel:

  9.  

    创建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);
                
    
    
            }
        }
    }
    View Code

    注:数据库中有7条记录,每页显示2条,所以在testFunc中,分别测试不同页码时,preCmd与nextCmd的可执行状态,且在最后一页时测试获取数据的Count

  10.  

    执行结果:

     

     本次实践已完成。

总结:MVVM前期需要花费一定的工作量,但带来的效果是显而易见的,当然,是否使用MVVM进行开发还需要很多其它因素的参考,希望大家灵活运用。

            

 

posted @ 2017-12-28 10:48  Coder_fang  阅读(3128)  评论(0编辑  收藏  举报