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進行開發還需要很多其它因素的參考,希望大家靈活運用。

            

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM