【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;//保存焦点所在行
}
{
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示例了,就不提供链接了。
posted on 2013-03-26 21:58 fish_1949 阅读(2527) 评论(7) 编辑 收藏 举报