.NET 2.0 的 ActionList






4.89/5 (50投票s)
2006年4月22日
5分钟阅读

195968

2463
Borland 的 ActionList 的实现。
引言
所有接触过 Borland Delphi 的人,都知道 Actions 的强大之处。Actions 用于连接各种 UI 元素,例如按钮、菜单项和工具栏按钮,使它们表现一致:链接的项会同时被选中/取消选中/启用/禁用,它们共享相同的 Text
和可能的 Image
属性,当然,点击时执行相同的代码。
使用代码
这个库基于两个主要组件:ActionList
类和 Action
类。ActionList
实现 IExtenderProvider
接口,包含一个 Actions 集合,并提供设计时支持,用于将 Action 与 ButtonBase
或 ToolStripItem
派生控件关联。因此,您首先将一个 ActionList
从工具箱拖放到您的 WinForm 上,然后就可以编辑它的 Actions
集合了。
添加并设置 Action 属性的所需值后,您可以将其链接到一个控件,您会注意到它的 Text
、Image
、Tooltip
、快捷键等属性值会被替换为连接的 Action 的值。
每个 Action 都暴露两个主要事件:Execute
和 Update
。每当链接的控件被点击时,都会引发 Execute
事件,所以人们应该捕获这个事件,而不是直接处理控件的 Click
事件。Update
事件在应用程序处于空闲状态时引发,用于在运行时启用/禁用链接的控件(当然,您也可以将其用于其他目的,例如设置它们的 CheckState
)。
Actions 在后台如何工作
我不会解释每一行代码:如果您对 .NET Framework 有一些经验,一切都会很简单易懂,所以我将只关注几个主要话题。
首先,每个 Action 内部都包含一个目标控件的集合。一旦添加了一个控件,它的属性就会根据 Action 的属性进行刷新,并且它的 Click
和 CheckStateChanged
事件会被处理(当然,一旦移除了一个目标,那些事件处理器也会被移除)。
internal void InternalAddTarget(Component extendee)
{
// we first add extendee to targets collection
targets.Add(extendee);
// we refresh its properties to Action's ones
refreshState(extendee);
// we add some handler to its events
AddHandler(extendee);
OnAddingTarget(extendee);
}
让我们先来看看目标属性的设置是如何处理的:所有工作都由私有的 updateProperty
方法完成。
private void updateProperty(Component target, string propertyName, object value)
{
WorkingState = ActionWorkingState.Driving;
try
{
if (ActionList != null)
{
if (!SpecialUpdateProperty(target, propertyName, value))
ActionList.TypesDescription[target.GetType()].SetValue(
propertyName, target, value);
}
}
finally
{
WorkingState = ActionWorkingState.Listening;
}
}
作为第一步,它将 Action 的工作状态更改为 Driving
(这会导致目标控件的事件处理被暂时禁用),然后将目标控件的属性设置为正确的值。唯一的问题是它通过反射工作,这可能非常慢,所以我构建了一个名为 ActionTargetDescriptionInfo
的特殊类,它缓存了类型的 PropertyInfo
以加快所有工作速度。
关于事件处理:当一个控件被添加到目标控件集合中时,Action 会捕获它的 Click
和 CheckStateChanged
事件。
protected virtual void AddHandler(Component extendee)
{
// Click event's handling, if present
EventInfo clickEvent = extendee.GetType().GetEvent("Click");
if (clickEvent != null)
{
clickEvent.AddEventHandler(extendee, clickEventHandler);
}
// CheckStateChanged event's handling, if present
EventInfo checkStateChangedEvent =
extendee.GetType().GetEvent("CheckStateChanged");
if (checkStateChangedEvent != null)
{
checkStateChangedEvent.AddEventHandler(extendee,
checkStateChangedEventHandler);
}
}
ClickEventHandler
和 checkStateChangedEventHandler
非常简单:第一个引发 Execute
事件,第二个根据 Action 的属性更新每个目标的 checkState
属性。
private void handleClick(object sender, EventArgs e)
{
if (WorkingState == ActionWorkingState.Listening)
{
Component target = sender as Component;
Debug.Assert(target != null, "Target is not a component");
Debug.Assert(targets.Contains(target),
"Target doesn't exist on targets collection");
DoExecute();
}
}
private void handleCheckStateChanged(object sender, EventArgs e)
{
if (WorkingState == ActionWorkingState.Listening)
{
Component target = sender as Component;
CheckState = (CheckState)ActionList.
TypesDescription[sender.GetType()].GetValue("CheckState", sender);
}
}
最后一点要解释的是 Update
事件是如何引发的:它由 ActionList
驱动,ActionList
处理 Application.Idle
事件并为每个拥有的 Action 引发此事件。
void Application_Idle(object sender, EventArgs e)
{
OnUpdate(EventArgs.Empty);
}
public event EventHandler Update;
protected virtual void OnUpdate(EventArgs eventArgs)
{
// we first raise ActionList's Update event
if (Update != null)
Update(this, eventArgs);
// next, we raise child actions update
foreach (Action action in actions)
{
action.DoUpdate();
}
}
如何创建您自己的自定义 Action
从 1.1.1.0 版本开始,Crad's Actions 库提供了更好的可扩展性支持。创建您自己的自定义 Action 非常简单:您只需创建一个继承自 Crad.Windows.Forms.Actions.Action
并用 StandardAction
属性标记它的新类。设计器支持由内部 ActionCollectionEditor
类的新实现提供,该类能够检查当前项目的引用,查找自定义 Actions。
这种行为是通过 ITypeDiscoveryService
设计器服务实现的,它使得解决一个非常棘手的程序集检查问题变得简单:在设计时查找类型可能是一项相当复杂的任务,因为当前项目可能尚未构建,并且相应的程序集可能根本不存在。使用 ITypeDiscoveryService
,一切都变得更容易,因此,现在 ActionCollectionEditor
有一个私有方法来完成这项工作;它看起来像下面的代码片段。
private Type[] getReturnedTypes(IServiceProvider provider)
{
List<Type> res = new List<Type>();
ITypeDiscoveryService tds = (ITypeDiscoveryService)
provider.GetService(typeof(ITypeDiscoveryService));
if (tds != null)
foreach (Type actionType in tds.GetTypes(typeof(Action), false))
{
if (actionType.GetCustomAttributes(typeof(
StandardActionAttribute), false).Length > 0 &&
!res.Contains(actionType))
res.Add(actionType);
}
return res.ToArray();
}
首先,它恢复对设计器的 ITypeDiscoveryService
的引用,然后调用其 GetTypes
方法来恢复所有可代码访问的、继承自 Action
并且被标记为 StandardAction
的类型。
关注点
Actions 可以帮助 Windows Forms 开发人员以一种非常简单高效的方式协调各种 UI 元素的行为。我希望它们能够链接到许多 .NET Framework 2.0 WinForms 控件(它们适用于所有 ButtonBase
或 ToolStripItem
派生控件),因此处理此要求的唯一方法是使用反射。但是,性能下降通过使用 PropertyInfo
缓存系统得到了补偿,该系统能够重用相同类型对象的元数据信息。
Crad's Actions 库还附带了一些特定目的的 Actions:例如,其中一些有助于处理剪贴板相关操作,如剪切、复制或粘贴,而另一些则在应用于 RichTextBox
时提供格式化功能。根据 Borland 的命名,它们被称为 Standard Actions,我还在开发它们,所以……期待在未来的版本中会有更多。
要更好地理解使用 Actions 实现复杂用户界面的便捷性,您可以查看作为演示应用程序提供的简单 RTF 编辑器。
历史
- 2006/04/30: Crad's Actions 1.1.1.0 发布。
- 增加了对创建自定义 Actions 的支持(本文有简要说明)。
- 增加了一些新的
StandardAction
s,例如ListView
actions 和About
action。
- 2006/04/22: 第一个版本。