【DevExpress】4、在WinForm里用反射模擬Web里面的超鏈接功能


 
        這幾個月一直在用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的。運行時示例圖:
     PriorityMenu
 
 
                DateFilterMenu
        
        當然,還有一些其他的類,以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;//保存焦點所在行

        }
 
操作挺復雜的,只看我們想看的,可以看到ShowModule分為初次加載和非初次加載,初次加載時一堆操作,非初次加載,只是恢復了上次的狀態;HideModule就簡單了,保存狀態即可。不用加載主頁面時加載太多數據,也不用每次切換分組都重復加載數據,提高了系統的速度,增加了用戶體驗,維護也方便了很多。
 
好了,就先總結這么多了,其他的再慢慢總結。。詳細代碼就看DevExpress的第一個WinForm示例了,就不提供鏈接了。
 
 

 


免責聲明!

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



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