• 【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示例了,就不提供链接了。
     
     
  • 相关阅读:
    Mysql开启日志
    amfphp传递负数的bug
    linux /var/spool/clientmqueue 目录占大量空间
    WinXP SSH连接不上虚拟机的解决方法
    批量数据替换助手V1.0版发布
    也谈反射的应用场景
    反射+特性打造简洁的AJAX调用
    文本处理之利器正则表达式闪亮登场
    关于缩略图的生成与访问策略的一些经验分享
    装饰模式个人的一些理解
  • 原文地址:https://www.cnblogs.com/ddhj/p/2983507.html
Copyright © 2020-2023  润新知