
這幾個月一直在用DevExpress做公司的一個工具,布局模仿DevExpress控件包里面的一個示例(如上圖),用Dev的朋友們應該對這個很熟悉吧#^_^#,從里面學到了許多東西,控件的基本使用,以及一些好的設計理念,哈哈,也不知道算不算理念,反正對我自己挺有幫助的,這里就總結了下,對自己是個鞏固,如果有不恰當的地方,請大家不要吝惜手中的磚頭。
布局很清晰,主體分為三部分:
①上面的Ribbon,放一命令按鈕以及導航組件或者查找條件等;
②左側的NavBarControl,起導航作用,其中包括Mail、Calendar、Contacts、Feeds、Tasks五個分組(NavBarGroup);
③右側的展示區,我們這里稱為Module(稱呼為單元或者組件皆可,我在這里都稱呼為單元)

主頁面看着不怎么復雜,但是在程序啟動時,若把每個分組下對應的數據頁面都加載,可以想象那會有多么多么的蝸牛。WinForm不像Web那樣,可以在左側指定右側的鏈接,然后在右側顯示鏈接的頁面。但我們可以利用反射來解決這個問題,這篇文章里就只講這一個知識點,其他的在后面再說。
先介紹下解決方案的構成,整體結構如下圖:




① Controls文件夾下放置我們定義的用戶控件,以及一個BackstageViewLable:

② Dictionaries,顧名思義,字典,語言包,切換語言用的,暫不講,因為我也沒用到,沒研究●﹏●
③ Forms文件夾下存放我們建立的窗體:

一般的窗體都以frm做前綴,特殊的是最后兩個窗體,ssMain是啟動窗體,wfMain是切換頁面時的等待窗體


④ Modules文件夾下存放也是用戶控件,但注意跟Controls中用戶控件的區別,沒有uc前綴,他們是被作為Module(就是上文所謂的單元)來使用的,在上面布局圖里所謂的Module區中的Panel里展示,主要是展示數據用的,當然也可以他用,之所以跟普通的用戶控件區別對待,因為是他們位置特殊,應該可以這樣理解吧,有待商酌。。。

⑤ Resources中存放圖片資源或者其他
⑥ Data類下存放的是要操作的數據的實體類,不明白這些類為什么不放到Data文件夾下,而是定義這樣一個“類”,查看方便?


⑦下面是兩個比較重要的“類”了,Controls 和 Helpers:


根據名字就可以大致推測他們的區別了,左側的Controls中存放的控件,右側的Helper中存放的幫助類,實際情況也是這樣,但我們可能會注意到上面已經有一個Controls文件夾了,這里為什么還要有這樣一個Controls“類”呢?
我自己的理解是這樣的:上面Controls文件夾中的控件是在開發環境中已經定義好的,可以在控制台中打開編輯的,而這里的控件是在運行時生成的,就像Web開發中在后台拼Html語句,哦,這個比喻不太恰當,因為這里是面向對象的,都是些類了,但就是這個意思了。例如這里的PriorityMenu、DateFilterMenu、BackstageViewLabel、ContactToolTipController這些類都是直接或者間接繼承UserControl的。運行時示例圖:


當然,還有一些其他的類,以Manager 結尾的是對一系列控件之間的組合操作類(好拗口●︿●); BaseControl類是自定義的控件基類,上面Controls文件夾中有一部分用戶控件是從這個累繼承來的;BaseModule類是單元的父類,上面Modules文件夾中的用戶控件都必須繼承這個類,“為什么”在后面講;剩下的用到的時候再講,現在先跳過。。。
Helpers中的類里面存放的都是些靜態類,就是這里的類都不能被實例化,如下:

⑧ Resources中存放一些枚舉、靜態變量、常數、字符串操作類(跟業務邏輯無關)、委托等全局信息:

打開后內容:

⑨ 剩下的很好理解了,frmMain.cs,主頁面了,就第一張圖示效果;Program.cs,開始啟動,進入主頁面~~~~
---------------------------------------------華麗的分割線,進入正題------------------------------------------------------
public partial class frmMain : RibbonForm { MailType currentMailType = MailType.Inbox; ModulesNavigator modulesNavigator; internal FilterColumnsManager FilterColumnManager; ZoomManager zoomManager; List<BarItem> AllowCustomizationMenuList = new List<BarItem>(); public frmMain() { InitializeComponent(); rpcSearch.Text = TagResources.SearchTools; InitNavBarGroups();//初始化NarBarGroups,主要是給左側的Group們的Tag綁定要在右側Module中加載的頁面
SkinHelper.InitSkinGallery(rgbiSkins);//初始化主題,在rgbiSkins中加載主題列表
RibbonButtonsInitialize();//初始化Ribbon中的控件們
modulesNavigator = new ModulesNavigator(ribbonControl1, pcMain);//初始化單元導航組件(ModulesNavigator)
zoomManager = new ZoomManager(ribbonControl1, modulesNavigator, beiZoom);//初始化伸縮管理組件
modulesNavigator.ChangeGroup(navBarControl1.ActiveGroup, this);//上面的InitNavBarGroups初始化了左側的Group,現在切換到第一個分組
NavigationInitialize();//初始化導航控件
SetPageLayoutStyle();//設置頁面的布局樣式
} ----上面是主頁面的構造函數,有個印象就好,這里要講的是切換分組時要用到反射的情況,下面也是此類中的一些關鍵方法,只講重點----
void InitNavBarGroups() { nbgMail.Tag = new NavBarGroupTagObject("Mail", typeof(DevExpress.MailClient.Win.Mail)); nbgCalendar.Tag = new NavBarGroupTagObject("Calendar", typeof(DevExpress.MailClient.Win.Calendar)); nbgContacts.Tag = new NavBarGroupTagObject("Contacts", typeof(DevExpress.MailClient.Win.Contacts)); nbgFeeds.Tag = new NavBarGroupTagObject("Feeds", typeof(DevExpress.MailClient.Win.Feeds)); nbgTasks.Tag = new NavBarGroupTagObject("Tasks", typeof(DevExpress.MailClient.Win.Tasks)); } //切換左側分組的操作,獲取Group中應該加載的用戶控件data,一般是列表之類的形式,連同分組e.Group作為參數傳遞過去,執行后續操作
private void navBarControl1_ActiveGroupChanged(object sender, DevExpress.XtraNavBar.NavBarGroupEventArgs e) { object data = GetModuleData((NavBarGroupTagObject)e.Group.Tag); modulesNavigator.ChangeGroup(e.Group, data); } protected object GetModuleData(NavBarGroupTagObject tag) { if(tag == null) return null; if (tag.ModuleType == typeof(DevExpress.MailClient.Win.Calendar)) return ucCalendar1; if(tag.ModuleType == typeof(DevExpress.MailClient.Win.Feeds)) return navBarControl2; if(tag.ModuleType == typeof(DevExpress.MailClient.Win.Tasks)) return nbgTasks; return null; }
---------------------------------N多代碼省略-------------------------------------
}
主頁面構造函數中這三行代碼至關重要
①先看第一行的InitNavBarGroups(),方法中的nbgMail、nbgCalendaer等就是左側的Group,然后分別給他們的Tag綁定了一個自定義的對象NavBarGroupTagObject,這樣就可以知道每個Group對應哪個Module了(Module即為單元,即為我們上面放於Modules文件夾下,沒有加uc的那些用戶控件)。嗯,我以前沒寫過WinForm,也幾乎沒用過Tag這個屬性,剛開始用的時候,看別人的代碼大部分都是往Tag里面添加數據,不管多大都放得下,什么都放,然后我也就濫用了。只到看到了這里的用法,才知道Tag也可以像Web里面的超鏈接一樣來用,存放所謂的“地址”,如下面的定義:
public class NavBarGroupTagObject { string name;//單元名稱
Type moduleType;//單元類型
BaseModule module;//單元,由父類定義
public NavBarGroupTagObject(string name, Type moduleType) { this.name = name; this.moduleType = moduleType; module = null; } public string Name { get { return name; } } public Type ModuleType { get { return moduleType; } } public BaseModule Module { get { return module; } set { module = value; } } }
②modulesNavigator = new ModulesNavigator(ribbonControl1, pcMain); 初始化了一個ModulesNavigator對象,下面是ModulesNavigator的定義。我把它稱呼為“單元導航組件”,怪怪的名字,這不是關鍵~~~~
- 由於不管切換到那個分組,主頁面總有一個單元,所以定義一個CurrentModule;
- 導航,所以定義一個ribbon,即最上面的RibbonControl 控件;
- 單元是動態加載到Panel中的,所以定義了一個panel,存放單元;
下面的類很關鍵啦,黃色的地方關鍵的關鍵,用到了反射,有注釋,先自己看下啦:
// 主框架,單元導航組件(包含有一個RibbonControl和一個PanelControl)
public class ModulesNavigator { /// <summary>
/// 當前組件中的單元,位於panel中 /// </summary>
public BaseModule CurrentModule { get { if (panel.Controls.Count == 0) //這里需要判斷一下,是因為初始狀態下panel里面沒有控件,是經后面反射動態加載進去的
return null; return panel.Controls[0] as BaseModule; } } RibbonControl ribbon; PanelControl panel; public ModulesNavigator(RibbonControl ribbon, PanelControl panel) { this.ribbon = ribbon; this.panel = panel; } /// <summary>
/// 切換NavBarControl中分組所執行的操作:①跟NavBarGroup對應的RibbonPage才可以顯示②在Panel中加載Tag對象中的Module /// </summary>
/// <param name="group">NavBarControl中切換到的分組</param>
/// <param name="moduleData">只在第一次加載時用到,注意這里不是單元中數據,而是左側Group中應該加載的用戶控件</param>
public void ChangeGroup(NavBarGroup group, object moduleData) { bool allowSetVisiblePage = true; NavBarGroupTagObject groupObject = group.Tag as NavBarGroupTagObject;//把Tag中綁定的對象取出來
if (groupObject == null) return;//檢查下group中tag屬性有沒有綁定對象,沒有的話就不執行后面操作了
#region 判斷哪些RibbonPage需要顯示,放到deferredPagesToShow序列中(延期顯示的Page們) List<RibbonPage> deferredPagesToShow = new List<RibbonPage>(); foreach (RibbonPage page in ribbon.Pages) { if (!string.IsNullOrEmpty(string.Format("{0}", page.Tag))) { bool isPageVisible = groupObject.Name.Equals(page.Tag); //要使Ribbon中的Page顯示,需要滿足如下兩個條件: //①當前選中分組的Tag屬性綁定的對象的name屬性值跟Ribbon中page的Tag屬性值相等 //②Ribbon中的page的Visible==True
if (isPageVisible != page.Visible && isPageVisible) deferredPagesToShow.Add(page); else page.Visible = isPageVisible; } if (page.Visible && allowSetVisiblePage) { ribbon.SelectedPage = page;//跟group對應的page可以顯示
allowSetVisiblePage = false; } } #endregion
#region 第一次加載 (InitModule)
bool firstShow = groupObject.Module == null;//是否是第一次Show,第一次加載時Module為空
if (firstShow) { if (SplashScreenManager.Default == null)//啟動畫面管理:如果沒有默認的,用wfMain做等待窗口
SplashScreenManager.ShowForm(ribbon.FindForm(), typeof(CN_Standards.ISPET.Win.Forms.wfMain), false, true); ConstructorInfo constructorInfoObj = groupObject.ModuleType.GetConstructor(Type.EmptyTypes);//獲得單位中沒有參數的那個構造函數(一般都只有一個無參構造函數) if (constructorInfoObj != null) { groupObject.Module = constructorInfoObj.Invoke(null) as BaseModule;//關鍵地方哦,用反射獲得分組綁定對象中的單元 groupObject.Module.InitModule(ribbon, moduleData);//這里是初始化Module對應的那個Group中的用戶控件,每個Module中初始化有差別 } if (SplashScreenManager.Default != null)//不為空,這里的moduleData為主頁面,此方法為主頁面構造函數中的那個ChangeGroup方法
{ Form frm = moduleData as Form;//可以把單元中的moduleData轉換成Form,
if (frm != null) SplashScreenManager.CloseForm(false, 2000, frm);//延遲2秒后關閉等待窗口,打開主頁面
else SplashScreenManager.CloseForm(); } } #endregion
#region 讓deferredPagesToShow序列中的RibbonPage顯示出來,並讓第一個被選中
foreach (RibbonPage page in deferredPagesToShow) {//讓跟group對應的page集合都顯示出來
page.Visible = true; } foreach (RibbonPage page in ribbon.Pages) {//選中第一個關聯的page
if (page.Visible) { ribbon.SelectedPage = page; break; } } #endregion
#region 再次加載以及離開此分組 (HideModule、ShowModule)
if (groupObject.Module != null) {//清空panel,再次把groupObject.Module填充到panel中
if (panel.Controls.Count > 0) { BaseModule currentModule = panel.Controls[0] as BaseModule; if (currentModule != null) currentModule.HideModule();//在各個Module中執行
} panel.Controls.Clear(); panel.Controls.Add(groupObject.Module); groupObject.Module.Dock = DockStyle.Fill;//填充滿
groupObject.Module.ShowModule(firstShow);//顯示單元,firstShow可能等於true,也可能是false
} #endregion } }
③第三行代碼就是上面類里面定義的方法了,切換分組的時候會加載分組“鏈接”的單元到panel中,但也是分三個過程的:
InitModule:初始化單元,第一次切換至分組時才執行,把單元實例化出來,然后弄進Panel里面;
ShowModule:顯示單元,每次切換分組時都執行,刪除上個分組的單元,加載新分組的單元;(就像web里面重繪框架里面的頁面)
HideModule:隱藏單元,在另個分組ShowModule前執行,主要做些保存狀態等操作,以便下次回到此分組時,可以恢復狀態。(類似web里面的ViewState )
groupObject.Module.InitModule(ribbon, moduleData);
引用上面類里的一行代碼,可以看到InitModule是在Module中執行的,其他兩個,ShowModule和HideModule也是在Module中執行的,為什么這樣呢?因為這里只是一個框架,提供了一些共有的操作,具體操作還得具體到每一個單元中,找一個單元,瞧一瞧:
internal override void InitModule(IDXMenuManager manager, object data)
{
base.InitModule(manager, data);//執行父類的初始化操作,就是這些Module有那些相同操作。。有點廢話
EditorHelper.InitPriorityComboBox(repositoryItemImageComboBox1);//初始化重要性設置下拉框
this.ribbon = manager as RibbonControl;
ucMailViewer1.SetMenuManager(manager);
ShowAboutRow();
}
internal override void ShowModule(bool firstShow) {
base.ShowModule(firstShow);
if(firstShow) {
filterCriteriaManager = new FilterCriteriaManager(gridView1);
filterCriteriaManager.AddBarItem(OwnerForm.ShowUnreadItem, gcIcon, "[Read] = 0");
filterCriteriaManager.AddBarItem(OwnerForm.ImportantItem, gcPriority, "[Priority] = 2");
filterCriteriaManager.AddBarItem(OwnerForm.HasAttachmentItem, gcAttachment, "[Attachment] = 1");
filterCriteriaManager.AddClearFilterButton(OwnerForm.ClearFilterItem);
SetPriorityMenu();
SetDateFilterMenu();
OwnerForm.FilterColumnManager.InitGridView(gridView1);
} else {
lockUpdateCurrentMessage = false;//解除鎖定
FocusRow(focusedRowHandle);//不是首次加載,選中上次移開時焦點所在行
}
gridControl1.Focus();
}
internal override void HideModule() {
lockUpdateCurrentMessage = true;//鎖定
focusedRowHandle = gridView1.FocusedRowHandle;//保存焦點所在行
}
{
base.InitModule(manager, data);//執行父類的初始化操作,就是這些Module有那些相同操作。。有點廢話
EditorHelper.InitPriorityComboBox(repositoryItemImageComboBox1);//初始化重要性設置下拉框
this.ribbon = manager as RibbonControl;
ucMailViewer1.SetMenuManager(manager);
ShowAboutRow();
}
internal override void ShowModule(bool firstShow) {
base.ShowModule(firstShow);
if(firstShow) {
filterCriteriaManager = new FilterCriteriaManager(gridView1);
filterCriteriaManager.AddBarItem(OwnerForm.ShowUnreadItem, gcIcon, "[Read] = 0");
filterCriteriaManager.AddBarItem(OwnerForm.ImportantItem, gcPriority, "[Priority] = 2");
filterCriteriaManager.AddBarItem(OwnerForm.HasAttachmentItem, gcAttachment, "[Attachment] = 1");
filterCriteriaManager.AddClearFilterButton(OwnerForm.ClearFilterItem);
SetPriorityMenu();
SetDateFilterMenu();
OwnerForm.FilterColumnManager.InitGridView(gridView1);
} else {
lockUpdateCurrentMessage = false;//解除鎖定
FocusRow(focusedRowHandle);//不是首次加載,選中上次移開時焦點所在行
}
gridControl1.Focus();
}
internal override void HideModule() {
lockUpdateCurrentMessage = true;//鎖定
focusedRowHandle = gridView1.FocusedRowHandle;//保存焦點所在行
}
操作挺復雜的,只看我們想看的,可以看到ShowModule分為初次加載和非初次加載,初次加載時一堆操作,非初次加載,只是恢復了上次的狀態;HideModule就簡單了,保存狀態即可。不用加載主頁面時加載太多數據,也不用每次切換分組都重復加載數據,提高了系統的速度,增加了用戶體驗,維護也方便了很多。
好了,就先總結這么多了,其他的再慢慢總結。。詳細代碼就看DevExpress的第一個WinForm示例了,就不提供鏈接了。