利用反射实现简单 UI 自动化
周融
(C) 2007 All Rights Reserved.
在实现分布式系统设计的过程中,在 UI 设计层往往会遇到这样的问题,我们希望建立一个用户界面工厂,其他开发人员和第三方软件公司的开发人员可以通过这个工厂很容易的产生界面扩充加载项。或者希望不需要写复杂的注册代码,就能够使某一种元数据的继承子类(如某个特定控件所对应的单击命令)自动全部注册到工厂中。本文通过介绍反射技术并通过一些实例让开发人员了解一种通过反射实现这种简单 UI 自动化的原理和方法。
在本文中:
1、问题
2、简单的处理方法
3、什么是反射
4、利用反射实现 UI 自动化
5、结论
问题
假设这样一个开发场景:在一个 Ribbon 界面应用程序中,所有的界面按钮元素都关联到特定的单击事件上,而单击事件处理程序又可以根据界面按钮的特性(如按钮名称)分辨出需要执行哪种动作,实现目标是尽量分散 UI 设计人员和逻辑编码人员之间的资源耦合。UI 设计人员只负责勾画 UI 并为每个按钮元素取名;逻辑编码人员则仅仅编写与该按钮对应的执行过程,至于如何关联它们,则由 UI 自动化自动完成。
这个例子看似简单,但分析起来并不是那么容易。首先,对于每一个截面按钮元素,到底是采用一个统一的单击事件入口,还是各自定义;其次,如何将每个按钮的单击事件中所需要执行的过程抽象出来;再次,如何建立按钮元素和这个抽象执行对象之间的关系;最后,如何解决自动关联按钮元素和执行对象。这一系列的问题,都不好解决。
利用建模工具和一些经验,发现必须描述一个统一的单击事件处理入口以方便 UI 自动化;另外,可以构建一个可继承的 Cmmand 类用来描述每个执行命令。我们发现,这样抽象出来原形后,需要解决如下问题。
1、如何寻找应用程序主窗体中所有的 Ribbon 按钮;
2、如何统一设定这些按钮的单击事件;
3、如何设计 Command 类;
4、如何使用 UI 自动化,将 Command 类的继承类和 Ribbon 按钮的单击事件处理程序自动结合起来。
只有解决了以上的问题,才能实现本文开头所提出的设计目标。
简单的处里方法
对于问题 1、2,寻找 Ribbon 按钮并自动增加单击事件并不难,让我们通过 Linq 来实现。请参考下面的代码:
private static void LoadMenuItems()
... {
// 图标
Program.ApplicationMainForm.Ribbon.ApplicationIcon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath).ToBitmap();
Program.ApplicationMainForm.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath);
// 对于每一个命令按钮,设置其默认的 Click 事件。
Command.Initialize();
var buttons = from item in Program.ApplicationMainForm.Ribbon.Items.OfType<DevExpress.XtraBars.BarItem>()
where item is DevExpress.XtraBars.BarButtonItem
select item;
foreach (var button in buttons)
...{
// 将每一个按钮对象关联到特定的命令对象上。
// 设置单击事件。
button.ItemClick += (object sender, DevExpress.XtraBars.ItemClickEventArgs e) =>
...{
Command cmd = Command.GetCommandByName(e.Item.Name);
if (cmd != null)
cmd.ExecuteCommand(new object(), new EventArgs());
};
}
}
这段代码为每一个 Ribbon 按钮设置了默认的 Click 事件。并且可以看到,如果 Click 事件通过 Command.GetCommandByName() 方法得到名称和按钮名称相同的 Command 实例。如果存在关联实例,则执行,否则不执行。这样我们就可以利用 Command 对象的 Add 和 Remove 方法动态增加/删除命令了。
我们可以把定义 Command 对象的类放在另一个程序集中,这样做的好处是可以供第三方开发人员调用并扩展 Command 的派生实例。并创建加载项。完整的 Command 定义如下:
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace Heading.LeafPsp.UI
... {
/**//// <summary>表示执行一个命令的事件处理委托。</summary>
/// <param name="sender">System.Object 的实例。</param>
/// <param name="e">System.EventArgs 的实例。</param>
public delegate void ExecuteCommandEventHandler(object sender, EventArgs e);
/**//// <summary>
/// 表示一个应用程序界面元素传递给 Windows 的一个命令。例如文件打开、应用程序退出等。