回《【開源】EnterpriseFrameWork框架系列文章索引》
EFW框架源代碼下載:http://pan.baidu.com/s/1qWJjo3U
EFW框架中的WinController控制器可以說是整個Winform版中最具有價值的地方,能夠熟練使用它的話,可以讓你寫得代碼結構清晰不知多少倍,真正的做到了CS開發的界面層與邏輯層的完全隔離;更重要的是改變了你寫界面代碼的思維,讓你一次性寫出功能完善的代碼,真的,傳統的那種事件驅動的方式編碼會讓你的代碼變得越來越臃腫,就算你懂得不斷重構你的代碼,也完全避免不了出現臃腫的情況;所以必須使用WinController控制器模式從源頭徹底解決這種問題;
WinController控制器與界面不一定是一一對應的,一個控制器可以對應多個界面,當幾個界面的操作十分密切時就可以用一個控制器來控制它們的行為;一個界面對應多個控制器這種方式最好不要這么做,這樣會讓你的程序變得復雜,也破壞了業務架構與框架之間的關系,參考第十一章《EnterpriseFrameWork框架的分層與系統業務的結合》。
本章主要內容通過解讀框架源代碼來學習WinController是怎么實現的,以及學習控制器這種設計模式;
本文要點:
1.如何使用Winform控制器
2.Winform控制器的設計思路
3.Winform控制器BaseController基類的實現代碼
4.Winform控制器的自定義標簽MenuAttribute和ViewAttribute
5.通過Winform控制器打開界面
Winform控制器源代碼目錄結構
EFW框架控制器設計圖
1.如何使用Winform控制器
如上圖,Books.Winform項目里的是界面文件,Books項目的是界面接口文件和控制器文件,frmBookManager對象繼承IfrmBook接口,bookwinController對象依IfrmBook接口;
見上圖,Books實例中保存數據功能代碼調用流程,界面代碼frmBookManager繼承了IfrmBook接口的兩個方法並實現,點擊保存按鈕,btnsave_Click事件內並不實現保存數據的代碼而只是向控制器發送一個消息,指定需要調用的方法名稱bookSave,程序開始執行控制器中的bookSave方法,利用接口屬性frmBook.currBook從界面獲取數據,並利用框架中的ORM保存到數據庫;接着調用GetBooks方法將數據顯示在界面上;GetBooks方法調用后台的IBookDao對象獲取Book數據,再通過frmBook.loadbooks(dt)接口方法將數據綁定到界面控件gridBook上;
2.Winform控制器的設計思路
Winform控制器的設計思路有點類似Web系統中的MVC模式,控制器處理這后台數據對象與前台界面直接的交互,利用接口從界面獲取數據,再傳遞給后台對象處理,處理完后再利用接口將數據傳遞給界面控件顯示;整個交互的邏輯代碼是在控制器中實現的,數據怎么從界面獲取與數據怎么綁定到界面控件都是在前端界面接口中實現的,而數據怎么存儲到數據庫與怎么從數據庫獲取數據都是在后端Dao或ObjectModel中實現的,當然也可以在控制器中利用oleDb直接操作數據庫,但好的程序結構還是放在Dao和ObjectModel中合適些;
Winform控制器必須繼承框架中的BaseController對象,還有控制器類名上要指定菜單標簽MenuAttribute和界面標簽ViewAttribute,BaseController對象封裝了控制器的一些處理功能,MenuAttribute和ViewAttribute一個對應系統的菜單名,一個決定控制器能操作的界面對象;
3.Winform控制器BaseController基類的實現代碼
因為所有Winform控制器都必須繼承BaseController基類,所以我們有必要先看一下BaseController基類的代碼是怎么樣的;
BaseController文件

1 /// <summary> 2 /// Winform控制器基類 3 /// </summary> 4 public abstract class BaseController : AbstractBusines 5 { 6 public AbstractDatabase oleDb 7 { 8 get 9 { 10 return _oleDb; 11 } 12 } 13 14 public SysLoginRight GetSysLoginRight 15 { 16 get 17 { 18 if (EFWCoreLib.CoreFrame.Init.AppGlobal.cache.GetData("RoleUser") != null) 19 { 20 return (SysLoginRight)EFWCoreLib.CoreFrame.Init.AppGlobal.cache.GetData("RoleUser"); 21 } 22 else 23 { 24 return new SysLoginRight(); 25 } 26 } 27 } 28 29 internal IBaseView _defaultView; 30 31 public IBaseView DefaultView 32 { 33 get { return _defaultView; } 34 } 35 36 private Dictionary<string, IBaseView> _iBaseView; 37 public Dictionary<string, IBaseView> iBaseView 38 { 39 get { return _iBaseView; } 40 set 41 { 42 _iBaseView = value; 43 foreach (KeyValuePair<string, IBaseView> val in _iBaseView) 44 { 45 //val.Value.ControllerEvent += new ControllerEventHandler(UI_ControllerEvent); 46 val.Value.InvokeController = new ControllerEventHandler(UI_ControllerEvent); 47 } 48 } 49 } 50 51 public CloseTab closeTab; 52 53 /// <summary> 54 /// 創建BaseController的實例 55 /// </summary> 56 public BaseController() 57 { 58 59 } 60 /// <summary> 61 /// 界面控制事件 62 /// </summary> 63 /// <param name="eventname">事件名稱</param> 64 /// <param name="objs">參數數組</param> 65 /// <returns></returns> 66 public virtual object UI_ControllerEvent(string eventname, params object[] objs) 67 { 68 switch (eventname) 69 { 70 case "Close": 71 if (closeTab != null) 72 closeTab(); 73 break; 74 case "this": 75 return this; 76 } 77 78 MethodInfo meth = this.GetType().GetMethod(eventname); 79 if (meth != null) 80 { 81 MethodAttribute[] WinM = (MethodAttribute[])meth.GetCustomAttributes(typeof(MethodAttribute), true); 82 if (WinM.Length > 0) 83 { 84 if (WinM[0].OpenDBKeys != null && WinM[0].OpenDBKeys.ToString().Trim() != "") 85 { 86 List<string> dbkeys = WinM[0].OpenDBKeys.Split(new char[] { ',' }).ToList(); 87 this.BindMoreDb(oleDb, "default"); 88 foreach (string dbkey in dbkeys) 89 { 90 EFWCoreLib.CoreFrame.DbProvider.AbstractDatabase _Rdb = EFWCoreLib.CoreFrame.DbProvider.FactoryDatabase.GetDatabase(dbkey); 91 _Rdb.WorkId = GetSysLoginRight.WorkId; 92 //創建數據庫連接 93 this.BindMoreDb(_Rdb, dbkey); 94 } 95 } 96 } 97 return meth.Invoke(this, objs); 98 } 99 return null; 100 } 101 102 /// <summary> 103 /// 初始化全局web服務參數對象 104 /// </summary> 105 public virtual void Init() { } 106 107 public virtual IBaseView GetView(string frmName) 108 { 109 return iBaseView[frmName]; 110 } 111 }
BaseController基類封裝了控制器的四方面的功能:
1)數據庫操作對象oleDb,這樣我們可以直接在控制器方法內使用它執行SQL語句操作數據庫
2)系統登錄后的用戶信息GetSysLoginRight,這樣我們可以再控制器中很方便的使用當前登錄的用戶信息;
3)界面對象字典iBaseView,這屬性與控制器上ViewAttribute標簽配置相對應,可以從中取出配置的界面接口對象來操作界面;
4)處理界面請求控制器方法的功能UI_ControllerEvent,界面使用InvokeController向控制器發送消息,而怎么效應界面的消息在此方法中實現的,利用反射機制執行對應的控制器方法;
4.Winform控制器的自定義標簽MenuAttribute和ViewAttribute
這里的設計是這樣的,一個控制器可以配置多個MenuAttribute,那么系統可以掛多個菜單,而這幾個菜單都是公用這一個控制器,同樣一個控制器可以配置多個ViewAttribute,控制器就可以同時操作這些配置的界面;
MenuAttribute文件

1 [AttributeUsageAttribute(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] 2 public class MenuAttribute:Attribute 3 { 4 5 string _defaultName; 6 7 /// <summary> 8 /// 菜單名稱 9 /// </summary> 10 public string DefaultName 11 { 12 get { return _defaultName; } 13 set { _defaultName = value; } 14 } 15 16 private string _defaultViewName; 17 /// <summary> 18 /// 菜單對應打開界面 19 /// </summary> 20 public string DefaultViewName 21 { 22 get { return _defaultViewName; } 23 set { _defaultViewName = value; } 24 } 25 26 string _memo; 27 public string Memo 28 { 29 get { return _memo; } 30 set { _memo = value; } 31 } 32 33 public MenuAttribute() 34 { 35 } 36 37 public MenuAttribute(string defaultName,string memo) 38 { 39 this._defaultName = defaultName; 40 this._memo = memo; 41 } 42 }
ViewAttribute文件

1 [AttributeUsageAttribute(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 2 public class ViewAttribute:Attribute 3 { 4 private string _name; 5 /// <summary> 6 /// 界面名稱 7 /// </summary> 8 public string Name 9 { 10 get { return _name; } 11 set { _name = value; } 12 } 13 14 private bool _defaultShow; 15 public bool DefaultView 16 { 17 get { return _defaultShow; } 18 set { _defaultShow = value; } 19 } 20 21 private Type _viewType; 22 /// <summary> 23 /// 界面對象類型 24 /// </summary> 25 public Type ViewType 26 { 27 get { return _viewType; } 28 set { _viewType = value; } 29 } 30 31 private string _dllName; 32 /// <summary> 33 /// 界面存放的DLL 34 /// </summary> 35 public string DllName 36 { 37 get { return _dllName; } 38 set { _dllName = value; } 39 } 40 41 private string _viewTypeName; 42 /// <summary> 43 /// 界面類型名稱 44 /// </summary> 45 public string ViewTypeName 46 { 47 get { return _viewTypeName; } 48 set { _viewTypeName = value; } 49 } 50 51 string _memo; 52 public string Memo 53 { 54 get { return _memo; } 55 set { _memo = value; } 56 } 57 }
5.通過Winform控制器打開界面
Winform系統打開一個界面是這樣操作的,用戶點擊系統主界面上的菜單,框架就會調用WinformViewCreator對象來實例化控制器對象和實例化控制器配置的所有界面窗體對象,所以我們有必要看一下WinformViewCreator對象是怎么實現的;
WinformViewCreator文件

1 public override Object InstanceController(CloseTab close) 2 { 3 try 4 { 5 6 //加載類庫 7 Assembly assembly = null; 8 assembly = Assembly.LoadFrom(AppGlobal.AppRootPath + "\\" + dllfile); 9 10 //獲得控制器類(型) 11 Type type = assembly.GetType(controllername, false, true); 12 13 MenuAttribute[] menuAttribute = (MenuAttribute[])type.GetCustomAttributes(typeof(MenuAttribute), true); 14 ViewAttribute[] viewAttribute = (ViewAttribute[])type.GetCustomAttributes(typeof(ViewAttribute), true); 15 //ServiceAttribute[] serviceAttribute = (ServiceAttribute[])type.GetCustomAttributes(typeof(ServiceAttribute), true); 16 17 if (menuAttribute.Length > 0) 18 { 19 Dictionary<string, IBaseView> viewDic = new Dictionary<string, IBaseView>(); 20 //Dictionary<string, SoapHttpClientProtocol> serviceDic = new Dictionary<string, SoapHttpClientProtocol>(); 21 BaseController controller = (BaseController)System.Activator.CreateInstance(type); 22 23 EFWCoreLib.CoreFrame.DbProvider.AbstractDatabase Rdb = EFWCoreLib.CoreFrame.DbProvider.FactoryDatabase.GetDatabase(); 24 Rdb.WorkId = controller.GetSysLoginRight.WorkId; 25 controller.BindDb(Rdb, AppGlobal.container); 26 27 controller.closeTab = close;//關閉窗口 28 29 for (int index = 0; index < viewAttribute.Length; index++) 30 { 31 if (viewAttribute[index].ViewType == null) 32 { 33 if (string.IsNullOrEmpty(viewAttribute[index].DllName)) 34 { 35 continue; 36 } 37 Assembly _assembly = Assembly.LoadFrom(AppGlobal.AppRootPath + "\\" + viewAttribute[index].DllName); 38 viewAttribute[index].ViewType = _assembly.GetType(viewAttribute[index].ViewTypeName, false, true); 39 } 40 IBaseView view = (IBaseView)System.Activator.CreateInstance(viewAttribute[index].ViewType); 41 if (viewAttribute[index].DefaultView) controller._defaultView = view; 42 if (index == 0 && viewAttribute.ToList().FindIndex(x => x.DefaultView == true) == -1) controller._defaultView = view; 43 viewDic.Add(viewAttribute[index].ViewType.Name, view); 44 } 45 //for (int index = 0; index < serviceAttribute.Length; index++) 46 //{ 47 // SoapHttpClientProtocol service = (SoapHttpClientProtocol)System.Activator.CreateInstance(serviceAttribute[index].ServiceType); 48 // serviceDic.Add(serviceAttribute[index].ServiceType.Name, service); 49 //} 50 51 controller.iBaseView = viewDic; 52 //controller.service = serviceDic; 53 54 controller.Init();//初始化 55 return controller; 56 } 57 58 return null; 59 } 60 catch (Exception err) 61 { 62 throw err; 63 } 64 }
上面代碼中有兩點需要說明一下,首先根據菜單配置的控制器名稱通過反射實例化控制器對象;
接着根據控制器的配置的ViewAttribute標簽,實例化界面對接並添加到iBaseView屬性中;