Creating a Most Recent Used Command Header-如何创建一个命令头类,使其呈现为菜单中的动态项列表?
摘要
-
本用例将使您掌握
-
CAAAfrMRUHeader 用例
- CAAAfrMRUHeader 的功能
- CAAAfrMRUHeader 的启动方法
- CAAAfrMRUHeader 代码所在位置
-
分步实现
-
简要总结
-
参考资料
本用例您将学到的内容
- 创建代表命令头的组件
该组件必须继承自CATAfrDialogCommandHeader类。 - 创建实例化图形呈现形式的组件
该组件必须继承自CATAfrCommandHeaderRep类,并实例化一个或多个CATDlgPushItem类对象。 - 创建控制图形呈现所用数据的组件
所用数据为一个字符串列表,该列表与 V5 文档实例无关。这意味着无论当前处于哪个文档,甚至在未打开任何文档的情况下,该字符串列表均保持一致。
CAAAfrComboColorHeader 用例 [1],该用例介绍了另一种自定义命令头。在该用例中,图形呈现形式为工具栏中的一个组合框。与当前用例不同的是,其数据(当前颜色)与文档相关联。为充分理解本文内容,建议您先阅读技术文章《命令头》[2],尤其是创建自定义命令头章节。
CAAAfrMRUHeader 用例
CAAAfrMRUHeader 的功能
- 启动 CNEXT
- 选择文件菜单

在 “退出” 选项上方有一条分隔线,分隔线正上方就是标准的最近使用文档列表。
- 在常用工具栏中,单击在 MRU 中添加项按钮。
![图片]()
该工具栏来自通用工作台的一个外接程序。如需显示此工具栏,请参阅相关用例 [3]。
在 MRU 中添加项命令是一个 CATDlgDialog 类型的命令。
- 随即弹出如下对话框。
![图片]()
在编辑器中输入 “Item 1”,然后点击 “确定”。
- 选择文件菜单
![图片]()
现在,在 “退出” 下方会出现一个新的按钮项:Item 1。
- 在通用工具栏中,单击在最近使用列表中添加项按钮。
![图片]()
![图片]()
在编辑框中输入 Item 2,然后单击确定。
- 选择文件菜单
![图片]()
现在,在 “退出” 下方会出现两个按钮项:
- 第一个位置:Item 2 —— 最后创建(使用)的项
- 第二个位置:Item 1 —— 最早创建(使用)的项
- 在文件菜单中选择 Item 1。
![图片]()
弹出所选项目对话框,其中显示您选择的项目(Item 1)。关闭该对话框即可。
- 选择文件菜单
现在,在 “退出” 下方依然显示两个按钮项,但请注意它们的新顺序:- 第一个位置:Item 1 —— 最近使用的项
- 第二个位置:Item 2 —— 较早使用的项
- 在文件菜单中单击新建。
- 在新建对话框中单击任意一种文档类型,然后单击确定。
- 选择文件菜单,您会看到项目列表保持不变。
- 会话启动时该列表始终为空(本用例中不涉及文件保存操作)
- 列表通过在 MRU 中添加项命令进行填充
- 列表会重新排序,将被选中的字符串置于首位
- 该列表与文档无关。无论打开哪个文档,甚至在未打开任何文档时,显示的列表均保持一致
该列表(称为MRU 列表)由一个名为 CAAAfrMRUManager 的组件维护。它通过 CAAIAfrMRUManagement 接口管理数据(字符串列表),这些数据会以菜单项按钮的形式显示。由于 MRU 列表是全局唯一的,因此该组件在整个会话期间也保持唯一。
图 1 MRU 列表管理器

MRU 标题是派生自 CATAfrDialogCommandHeader 类的一个实例,与任何图形表现形式为自定义样式的命令标题类似。下面的 UML 图详细描述了该类结构:
图 2 MRU 标题类 UML 图

CAAAfrMRUHeader 是一个组件,它必须实现 CATIAfrCommandHeaderRep 接口以提供自定义的图形表现形式。该接口包含三个方法:
- CreateCtxMenuRep 与 CreateToolbarRep:无返回值
- CreateMenuRep:
它会实例化一个 CAAAfrMRURep 类的实例,该类的说明如下。
图 3 命令标题图形化表示 UML 图

如何启动 CAAAfrMRUHeader
- Windows 系统路径:
InstallRootDirectory\CAAApplicationFrame.edu\CNext\code\dictionary\ - UNIX 系统路径:
InstallRootDirectory/CAAApplicationFrame.edu/CNext/code/dictionary/
在该文件中,删除以下两行代码行首的注释符 #:
#CAAAfrGeneralWksAddin CATIWorkbenchAddin libCAAAfrGeneralWksAddin
#CAAAfrGeneralWksAddin CATIAfrGeneralWksAddin libCAAAfrGeneralWksAddin
修改完成后,执行 mkCreateRuntimeView 命令。
CAAAfrMRUHeader 代码位置
- Windows 系统路径:
InstallRootDirectory\CAAApplicationFrame.edu - Unix 系统路径:
InstallRootDirectory/CAAApplicationFrame.edu
其中 InstallRootDirectory 为 CAA 安装光盘的安装目录。
CAAAfrCustCommandHdrModel.m 模块
- CAAAfrMRUManagerNotification.h(LocalInterfaces) 和 CAAAfrMRUManagerNotification.cpp(src)
用于在列表中添加元素,或选中某个元素(使其成为列表首项)时发送的通知类。 - CAAIAfrMRUManagement.cpp(src)、CAAIAfrMRUManagement.h(PublicInterfaces) 以及 TIE_CAAIAfrMRUManagement.tsrc(src)
用于实现添加元素、选中元素、获取列表的接口。 - CAAAfrMRUManager.h(LocalInterfaces) 和 CAAAfrMRUManager.cpp(src)
CAAAfrMRUManager 组件本身,该组件实现了 CAAIAfrMRUManagement 接口。 - CAAAfrGetMRUManager.cpp(src)、CAAAfrGetMRUManager.h(PublicInterfaces)
用于在会话期间获取全局唯一的 CAAAfrMRUManager 组件的全局函数。
CAAAfrCustomizedCommandHeader.m 模块包含用于定义 MRU 标题的类:
- CAAAfrMRURep.h(本地接口)与 CAAAfrMRURep.cpp(源文件)
- CAAEAfrCommandHeaderRepForMRU.h(本地接口)与 CAAEAfrCommandHeaderRepForMRU.cpp(源文件)
- CAAAfrMRUHeader.cpp(源文件)、CAAAfrMRUHeader.h(私有接口)
CAAfrGeoCommands.m 模块包含各类 CATCommand 类:
- CAAAfrMRUAddElementCmd.h(本地接口) 和 CAAAfrMRUAddElementCmd.cpp(源文件)
该类属于 CATDlgDialog 类(见图示),在最终用户单击通用工具栏中的 **“在 MRU 中添加项”** 命令时执行。 - CAAAfrMRUSelElementCmd.h(本地接口) 和 CAAAfrMRUSelElementCmd.cpp(源文件)
该类属于 CATDlgDialog 类(见图示),在最终用户从 MRU 列表中选择某一项时执行。
CAAAfrGeneralWksAddin.m 模块包含通用工作台的一个外接程序(Add‑in):
- CAAAfrGeneralWksAdn.h(本地接口)
- CAAAfrGeneralWksAdn.cpp(源文件)
分步操作
- 创建 CAAAfrMRUManager 组件
- 创建表示 MRU 命令标题的组件
- 创建用于实例化图形表现形式的类
- 实例化 MRU 标题类
创建 CAAAfrMRUManager 组件
// 定义MRU列表最大存储数量为5
#define MRU_MAX_SIZE 5
// CAAAfrMRUManager 类:继承自 CATBaseUnknown 基类
class CAAAfrMRUManager : public CATBaseUnknown
{
// 声明CAA组件类(固定宏定义)
CATDeclareClass;
public:
// 构造函数
CAAAfrMRUManager();
// 析构函数
virtual ~CAAAfrMRUManager();
// 静态方法:获取全局唯一的MRU管理器实例
static HRESULT GetMRUManager(CAAAfrMRUManager ** oManager);
// 虚方法:向MRU列表中添加新元素
virtual HRESULT AddElement(CATUnicodeString &iNewElement) ;
// 虚方法:获取MRU元素列表(const 修饰,不可修改列表)
virtual HRESULT GetElementList(CATListOfCATUnicodeString &ElementList) const ;
// 虚方法:根据索引位置选中列表中的元素
virtual HRESULT SelectElement(int iPosition) ;
private:
// 拷贝构造函数(私有化,禁止拷贝)
CAAAfrMRUManager(const CAAAfrMRUManager &iObjectToCopy);
// 赋值运算符重载(私有化,禁止赋值)
CAAAfrMRUManager & operator = (const CAAAfrMRUManager &iObjectToCopy);
private:
// 静态成员:配置清理控制器
static CATIniCleanerSettingCtrl _Cleaner ;
// 存储MRU字符串列表
CATListOfCATUnicodeString _NameList;
};
- CATDeclareClass 宏用于声明该类属于一个 CAA 组件。
- GetMRUManager 是一个静态方法,用于保证在整个会话期间仅存在一个 CAAAfrMRUManager 组件实例。无论是否打开文档、打开哪个文档,该列表均为全局唯一。
- AddElement、GetElementList 和 SelectElement 是 CAAIAfrMRUManagement 接口的方法。
- 拷贝构造函数与赋值运算符重载在源文件中未实现。这可以避免编译器在不知情的情况下自动将其生成为公有成员。
- _Cleaner 持有指向唯一 CAAAfrMRUManager 组件实例的指针。当会话结束时,_Cleaner 实例会被销毁,同时释放 CAAAfrMRUManager 类的指针,从而避免出现 “MLK” 内存泄漏问题。在你自己的代码中,静态数据可以直接设为指向 CAAAfrMRUManager 类实例的指针。
- _NameList 是一个 CATUnicodeString 类型的列表,该列表最多容纳 5 个元素(MRU_MAX_SIZE = 5)。
CAAAfrMRUManager 类的源文件如下所示:
...
// 初始化静态清理器成员
CATIniCleanerSettingCtrl CAAAfrMRUManager::_Cleaner ;
// CAA 组件实现宏:注册 CAAAfrMRUManager 类
CATImplementClass(CAAAfrMRUManager, Implementation, CATBaseUnknown , CATNull);
// 包含接口绑定头文件
#include <TIE_CAAIAfrMRUManagement.h>
// 将 CAAAfrMRUManager 与 CAAIAfrMRUManagement 接口绑定
TIE_CAAIAfrMRUManagement(CAAAfrMRUManager);
// 构造函数
CAAAfrMRUManager::CAAAfrMRUManager() {}
// 析构函数
CAAAfrMRUManager::~CAAAfrMRUManager(){}
...
- _Cleaner 已完成初始化。
- CATImplementClass 宏将 CAAAfrMRUManager 类声明为组件主类(Implementation 类型),使其从 CATBaseUnknown 进行对象模型派生 [11]。
- 通过 TIE_CAAIAfrMRUManagement 宏,CAAAfrMRUManager 类声明自身实现了 CAAIAfrMRUManagement 接口。
...
HRESULT CAAAfrMRUManager::GetMRUManager(CAAAfrMRUManager ** oManager)
{
...
// 从清理器中获取已存在的控制器实例
CATBaseUnknown * pManager = _Cleaner.GetController();
// 如果实例不存在
if ( NULL == pManager )
{
CAAAfrMRUManager * Obj = NULL;
// 创建唯一的 CAAAfrMRUManager 实例
Obj = new CAAAfrMRUManager();
// 内存不足,创建失败
if ( NULL == Obj )
{
rc = E_OUTOFMEMORY ;
}
else
{
// 返回创建好的实例
*oManager = Obj ;
// 将实例交给清理器管理
_Cleaner.SetController(Obj);
}
}
// 如果实例已存在
else
{
// 直接返回已存在的唯一实例
*oManager = (CAAAfrMRUManager *) pManager ;
}
...
GetMRUManager 方法用于创建 CAAAfrMRUManager 类的唯一实例,或获取已存在的实例。_Cleaner 是一个静态数据成员,属于 CATIniCleanerSettingCtrl 类的实例。如果 CATIniCleanerSettingCtrl 类的 GetController 方法返回空指针,则创建一个 CAAAfrMRUManager 类实例,并通过 SetController 方法由 _Cleaner 持有该实例。否则,GetMRUManager 方法将返回由 _Cleaner 持有、并通过 GetController 方法获取到的指针。
现在,我们来看 CAAIAfrMRUManagement 接口的三个方法。
...
HRESULT CAAAfrMRUManager::AddElement(CATUnicodeString &iNewElement)
{
// 如果列表已满
if ( MRU_MAX_SIZE == _NameList.Size() )
{
// 移除列表最后一个元素
_NameList.RemovePosition(MRU_MAX_SIZE);
}
// 将新元素插入到列表首位
_NameList.InsertBefore(1,iNewElement);
// 获取回调管理器并发送通知
CATCallbackManager * pCBManager = ::GetDefaultCallbackManager(this) ;
if ( NULL != pCBManager )
{
CAAAfrMRUManagerNotification * pNotification = new CAAAfrMRUManagerNotification();
pCBManager->DispatchCallbacks(pNotification,this);
pNotification->Release();
}
return S_OK ;
}
...
当数据成员 _NameList 所表示的列表中元素数量少于 MRU_MAX_SIZE 时,AddElement 方法会直接将新元素(iNewElement)添加到列表的第一个位置(InsertBefore(1,...))。但当列表中元素数量已达到 MRU_MAX_SIZE 上限时,会先移除列表最后一个元素,再将新元素添加到首位。
AddElement 方法的第二部分是借助回调管理器 [7] 发送一条通知。这样,所有订阅了 MRU 管理器所发布的这一事件的对象,都将收到通知并被激活。关于如何创建类似 CAAAfrMRUManagerNotification 这样的通知类,以及如何使用默认回调管理器发送通知,可参考回调相关用例 [8]。
...
HRESULT CAAAfrMRUManager::GetElementList(CATListOfCATUnicodeString & oElementList) const
{
for ( int i = 1 ; i <= _NameList.Size() ; i++)
{
oElementList.Append(_NameList[i]);
}
return S_OK ;
}
...
GetElementList 方法返回由数据成员 _NameList 维护的项目列表内容。MRU 标题会调用该方法来构建其图形显示界面,详见 “创建实例化图形表示的类” 这一步骤。
...
HRESULT CAAAfrMRUManager::SelectElement(int iPosition)
{
HRESULT rc = E_FAIL ;
if ( (iPosition >= 1) && (iPosition <= MRU_MAX_SIZE) )
{
CATUnicodeString Sel = _NameList[iPosition] ;
_NameList.RemovePosition(iPosition);
_NameList.InsertBefore(1,Sel);
CATCallbackManager * pCBManager = ::GetDefaultCallbackManager(this) ;
...
CAAAfrMRUManagerNotification * pNotification = new CAAAfrMRUManagerNotification();
pCBManager->DispatchCallbacks(pNotification,this);
pNotification->Release();
pNotification = NULL ;
...
SelectElement 方法的作用是将列表中第 iPosition 个元素移至首位。具体操作为:先将该元素从列表中移除(RemovePosition),再将其重新插入到列表第一个位置(InsertBefore)。
创建表示 MRU 命令标题的组件
组件创建
// 应用框架模块
#include "CATAfrDialogCommandHeader.h"
class ExportedByCAAAfrCustomizedCommandHeader CAAAfrMRUHeader: public CATAfrDialogCommandHeader
{
CATDeclareClass;
public:
CAAAfrMRUHeader(const CATString & iHeaderName);
virtual ~CAAAfrMRUHeader();
CATCommandHeader * Clone();
private:
CAAAfrMRUHeader(CATCommandHeader *iHeaderToCopy);
CAAAfrMRUHeader(const CAAAfrMRUHeader & iObjectToCopy);
CAAAfrMRUHeader& operator = (const CAAAfrMRUHeader & iObjectToCopy);
};
CAAAfrMRUHeader 继承自 CATAfrDialogCommandHeader。对于需要自定义图形表现形式的命令标题而言,这一继承关系是强制要求的。CATDeclareClass 宏用于声明该类属于一个 CAA 组件。CATDeclareHeaderResources 宏会插入用于管理命令标题资源的相关方法。
关于必须实现的公有方法
- 以 const CATString 引用为参数的构造函数
- 析构函数
- 从 CATCommandHeader 继承而来的 Clone 方法,用于复制命令标题实例。有关 Clone 方法的完整说明,请参考关于命令标题的技术文章中 “自定义命令标题类结构” 一节 [2]。
关于必须定义的私有方法
- 接收 CATCommandHeader 指针的构造函数,专门供 Clone 方法使用。
- 另外两个构造函数被声明为私有,且在源文件中不予实现。这可以避免编译器在未告知的情况下自动将其生成为公有方法。
以下是 CAAAfrMRUHeader 的源文件内容:
#include "CAAAfrMRUHeader.h"
CATImplementClass(CAAAfrMRUHeader,
Implementation,
CATAfrDialogCommandHeader,
CATNull);
CAAAfrMRUHeader::CAAAfrMRUHeader(const CATString & iHeaderName) :
CATAfrDialogCommandHeader(iHeaderName){}
CAAAfrMRUHeader::CAAAfrMRUHeader(){}
CATCommandHeader * CAAAfrMRUHeader::Clone ()
{
return new CAAAfrMRUHeader(this);
}
CAAAfrMRUHeader::CAAAfrMRUHeader(CATCommandHeader * iHeaderToCopy):
CATAfrDialogCommandHeader(iHeaderToCopy)
{}
- 自定义命令标题是一个 CAA 组件。CATImplementClass 宏将 CAAAfrMRUHeader 类声明为组件主类(Implementation 类型),使其从 CATAfrDialogCommandHeader 进行对象模型派生 [11]。
- Clone 方法返回当前对象的一个拷贝构造实例。
注意:头文件中未使用 CATDeclareHeaderResources 宏,源文件中也未使用 CATImplementHeaderResources 宏。该标题不包含多语言(NLS)资源 [10]。
CATIAfrCommandHeaderRep 接口实现
...
class CAAEAfrCommandHeaderRepForMRU : public CATBaseUnknown
{
CATDeclareClass;
public:
CAAEAfrCommandHeaderRepForMRU ();
virtual ~CAAEAfrCommandHeaderRepForMRU();
virtual HRESULT CreateToolbarRep (const CATDialog * iParent,
CATAfrCommandHeaderRep ** oHdrRep) ;
virtual HRESULT CreateMenuRep (const CATDialog * iParent,
CATAfrCommandHeaderRep ** oHdrRep) ;
virtual HRESULT CreateCtxMenuRep (const CATDialog * iParent,
CATAfrCommandHeaderRep ** oHdrRep) ;
private:
CAAEAfrCommandHeaderRepForMRU (const CAAEAfrCommandHeaderRepForMRU &iObjectToCopy);
CAAEAfrCommandHeaderRepForMRU & operator = (const CAAEAfrCommandHeaderRepForMRU &iObjectToCopy);
};
CATDeclareClass 宏声明 CAAEAfrCommandHeaderRepForMRU 属于一个 CAA 组件。
CreateToolbarRep、CreateMenuRep 和 CreateCtxMenuRep 是 CATIAfrCommandHeaderRep 接口的方法。
以下是 CAAEAfrCommandHeaderRepForMRU 的源文件内容:
...
#include <TIE_CATIAfrCommandHeaderRep.h>
TIE_CATIAfrCommandHeaderRep(CAAEAfrCommandHeaderRepForMRU);
CATImplementClass(CAAEAfrCommandHeaderRepForMRU,
DataExtension,
CATBaseUnknown,
CAAAfrMRUHeader);
};
CAAEAfrCommandHeaderRepForMRU::
CAAEAfrCommandHeaderRepForMRU():CATBaseUnknown(){}
CAAEAfrCommandHeaderRepForMRU::~CAAEAfrCommandHeaderRepForMRU(){}
...
CAAEAfrCommandHeaderRepForMRU 类通过 TIE_CATIAfrCommandHeaderRep 宏声明自身实现了 CATIAfrCommandHeaderRep 接口。
CATImplementClass 宏借助 DataExtension 关键字,将 CAAEAfrCommandHeaderRepForMRU 类声明为数据扩展类,用于扩展 CAAAfrMRUHeader 组件。对于任何类型的扩展类,第三个参数必须始终设为 CATBaseUnknown 或 CATNull。
类的构造函数与析构函数均为空实现。
...
HRESULT CAAEAfrCommandHeaderRepForMRU::CreateMenuRep
(const CATDialog * iParent,CATAfrCommandHeaderRep ** oHdrRep)
{
HRESULT rc = E_FAIL ;
if ( oHdrRep != NULL )
{
CATString Name = "CAAAfrMRURepId" ;
CAAAfrMRURep * pMRURep = new CAAAfrMRURep(iParent,Name);
*oHdrRep = (CATAfrCommandHeaderRep *) pMRURep ;
rc = S_OK ;
}
return rc ;
}
...
CreateMenuRep 方法提供用于实例化 MRU 标题图形表现的类。每当该命令标题需要在菜单中显示时,系统就会调用此方法。
CAAAfrMRURep 类是一个 CATCommand 类(见图 3),负责实例化 MRU 标题的图形界面(即 CATDlgPushItem 实例)。下一节 “创建实例化图形表现的类” 会对其进行详细说明。
iParent是一个 CATDialog 组件,将作为所有 CATDlgPushItem 实例的对话框父对象。Name是 CAAAfrMRURep 类实例的命令名称,所有 CAAAfrMRURep 实例可使用相同标识符。
无需关心 CAAAfrMRURep 实例的销毁工作:返回值 oHdrRep 由应用框架托管,该指针会被自动释放。
...
HRESULT CAAEAfrCommandHeaderRepForMRU::
CreateToolbarRep(const CATDialog * iParent,CATAfrCommandHeaderRep ** oHdrRep)
{
return E_FAIL ;
}
HRESULT CAAEAfrCommandHeaderRepForMRU::
CreateCtxMenuRep (const CATDialog * iParent,CATAfrCommandHeaderRep ** oHdrRep)
{
return E_FAIL;
}
该 MRU 标题不在工具栏和右键菜单中显示,因此 CreateToolbarRep 与 CreateCtxMenuRep 均返回 E_FAIL。
创建实例化图形表现的类
- 设置回调,以便在 MRU 列表的内容或顺序发生变化时接收通知
- 根据 MRU 列表创建 CATDlgPushItem 实例
- 启动命令,在对话框中(见图)显示选中的项目
以下是 CAAAfrMRURep 头文件:
...
#define MRU_MAX_SIZE 5
class CAAAfrMRURep : public CATAfrCommandHeaderRep
{
public:
CAAAfrMRURep (const CATDialog * iParent, CATString & iCommandName);
virtual ~CAAAfrMRURep();
HRESULT Build();
private:
void SelectCB(CATCommand * iPublishingCommand,
CATNotification * iNotification,
CATCommandClientData iData);
void ModifiedCB(CATCallback iEvent,
void * ,
CATNotification * iNotification,
CATCallbackEvent iData,
CATSubscriberData iCallBack);
HRESULT ModifyListItem() ;
CAAAfrMRURep (const CAAAfrMRURep &iObjectToCopy);
CAAAfrMRURep & operator = (const CAAAfrMRURep &iObjectToCopy);
private:
CATDlgPushItem * _pItemList[MRU_MAX_SIZE];
CAAIAfrMRUManagement * _pIAfrMRUManagement ;
};
- CAAAfrMRURep 类继承自 CATAfrCommandHeaderRep 类,如图 3 所示。
- Build 方法是 CATAfrCommandHeaderRep 类中的方法,且为纯虚方法,必须重写。该方法会在 CAAAfrMRURep 类实例化之后,也就是 CreateMenuRep 方法调用完成后,由应用框架立即调用。
- 私有部分:
- SelectCB 方法是一个回调方法,用于响应用户在动态列表中选择某一项的操作。
- ModifiedCB 方法是一个回调方法,当数据模型发送 CAAAfrMRUNotification 通知时被调用,详见第一步。
- ModifyListItem 方法由 Build 和 ModifiedCB 调用,用于创建或更新 CATDlgPushItem 实例列表。
- 数据成员:
- _pItemList:在 ModifyListItem 方法中创建的 CATDlgPushItem 类实例数组。
- _pIAfrMRUManagement:指向 CAAAfrMRUManager 组件的 CAAIAfrMRUManagement 接口指针。
CAAAfrMRURep 源文件内容如下:
类的构造函数
...
CAAAfrMRURep::CAAAfrMRURep(const CATDialog * iParent,CATString & iCommandName):
CATAfrCommandHeaderRep(iParent,iCommandName)
,_pIAfrMRUManagement(NULL)
{
for ( int i = 0 ; i < MRU_MAX_SIZE ; i++ )
{
_pItemList[i] = NULL ;
}
...
第二步是获取管理 MRU 列表的组件。在本用例中,通过全局函数 CAAAfrGetMRUManager 获取唯一的 CAAAfrMRUManager 组件。该方法的第二个参数是一个指向由 CAAAfrMRUManager 组件实现的接口的指针。
...
HRESULT rc = ::CAAAfrGetMRUManager(IID_CAAIAfrMRUManagement,
(void**)&_pIAfrMRUManagement);
...
最后一步是设置回调方法,用于在 CAAAfrMRUManager 组件发送 CAAAfrMRUManagerNotification 通知时接收通知;换句话说,当列表中添加元素或选中元素时触发通知。
...
if ( SUCCEEDED(rc) )
{
::AddCallback(this,
_pIAfrMRUManagement,
"CAAAfrMRUManagerNotification",
(CATSubscriberMethod)&CAAAfrMRURep::ModifiedCB,
NULL);
}
}
- this:订阅者
- _pIAfrMRUManagement:发布者
- CAAAfrMRUManagerNotification:由发布者发送的通知类
- ModifiedCB:当收到 CAAAfrMRUManagerNotification 通知时,当前对象将被调用的方法
- NULL:回调方法无附加参数
析构函数
...
CAAAfrMRURep::~CAAAfrMRURep()
{
if ( NULL != _pIAfrMRUManagement )
{
// 移除当前对象在发布者上注册的所有回调
::RemoveSubscriberCallbacks(this, _pIAfrMRUManagement);
// 释放接口指针
_pIAfrMRUManagement->Release();
_pIAfrMRUManagement = NULL ;
}
for ( int i=0 ; i < MRU_MAX_SIZE ; i++)
{
if ( NULL != _pItemList[i] )
{
// 延迟销毁菜单项控件
_pItemList[i]->RequestDelayedDestruction();
_pItemList[i] = NULL ;
}
}
...
}
最后,在构造函数中设置的回调必须从回调管理器中移除 [7],并且所有 CATDlgPushItem 实例都必须被释放。
Build 方法
...
HRESULT CAAAfrMRUoRep::Build()
{
ModifyListItem() ;
}
...
ModifiedCB 方法
...
void CAAAfrMRURep::ModifiedCB(CATCallback,
void *,
CATNotification * iNotification,
CATCallbackEvent,
CATSubscriberData)
{
ModifyListItem();
}
...
ModifyListItem 方法
- 从 CAAAfrMRUManager 组件中获取待显示的项目列表
- 检查
_pItemList中的 CATDlgPushItem 实例数量与待显示项目数量一致 - 为每个 CATDlgPushItem 设置标题
第一步是获取用于创建图形界面的对话框父对象。该信息由 CATAfrCommandHeaderRep 类保存,并通过其 GetDialogParent 方法获取。
...
HRESULT CAAAfrMRURep::ModifyListItem()
{
const CATDialog * pParent = NULL ;
GetDialogParent(&pParent);
...
随后,通过 CAAIAfrMRUManagement 接口的 GetElementList 方法从 CAAAfrMRUManager 中获取待显示项目列表。List 为该列表,SizeList 为列表中的元素个数。
...
CATListOfCATUnicodeString List ;
_pIAfrMRUManagement->GetElementList(List);
int SizeList = List.Size();
...
然后,针对列表中的第 i 个元素:如果在 CAAAfrMRURep 类的数据成员 _pItemList 的第 i 个位置上不存在 CATDlgPushItem 实例,则创建一个新实例:
- CATDlgPushItem
CATDlgPushItem 类的最后一个参数是对话框对象的标识符。建议使用带有序号的字符串作为其值。本例中使用MRUItem_num,其中num为循环索引。 - AddAnalyseNotificationCB
当最终用户选中菜单中的某一项时,该按钮项会发送GetMenuIActivateNotification通知,此通知将在SelectCB方法中处理,以启动显示所选项目名称的命令。 - CATINT32ToPtr
使用 CATINT32ToPtr 是为了兼容 64 位编译环境。 - SetFather
最后,修改该按钮项的 CATCommand 父对象。默认情况下,命令父对象即为对话框父对象,也就是按钮项将要被插入的容器pParent。如果不修改命令父对象,当前的 CAAAfrMRURep 实例(即 this)将无法接收到GetMenuIActivateNotification通知。可参考关于命令树结构的参考文章 [9]。
// 遍历 MRU 列表中的每一个元素
for (int i = 0; i < SizeList; i++)
{
// 将索引 (i+1) 转换为字符串,用于生成菜单项名称
CATUnicodeString num;
num.BuildFromNum(i + 1);
// 如果当前位置的菜单项还未创建
if (_pItemList[i] == NULL)
{
// 拼接菜单项名称,格式为 "MRUItem_1", "MRUItem_2"...
CATUnicodeString ItemName("MRUItem_");
ItemName += num;
// 创建一个新的菜单按钮项,并指定其父对话框
_pItemList[i] = new CATDlgPushItem((CATDialog *)pParent,
ItemName.CastToCharPtr());
// 为菜单项添加激活通知回调
// 当用户点击此项时,会调用 CAAAfrMRURep::SelectCB 方法
// 并将当前索引 i 作为参数传递
AddAnalyseNotificationCB(_pItemList[i],
_pItemList[i]->GetMenuIActivateNotification(),
(CATCommandMethod)&CAAAfrMRURep::SelectCB,
(CATCommandClientData)CATINT32ToPtr(i));
// 设置命令父对象为当前 CAAAfrMRURep 实例,确保能接收回调通知
_pItemList[i]->SetFather(this);
}
}
最后,对于由 CAAAfrMRUManager 组件维护、且在上方代码中获取到的列表 List 里的第 i 个元素,CAAAfrMRURep 类的数据成员 _pItemList 中会对应存在一个 CATDlgPushItem 类实例。
该按钮项的标题由元素在列表中的位置与项目名称拼接而成,例如:2 MyItemName。可参考 “文件菜单” 示意图。
...
num += " ";
num += List[i+1] ;
_pItemList[i]->SetTitle(num);
...
SelectCB 方法
_pItemList 中的某个元素发送通知时,该回调方法会被触发。此方法有两个作用:- 获取被选中的项目
CATPtrToINT32宏用于转换AddAnalyseNotificationCB方法中传入的参数数据,详见上文。 - 通知 CAAAfrMRUManager 某一项已被选中
_pIAfrMRUManagement是指向当前会话中唯一 CAAAfrMRUManager 组件的 CAAIAfrMRUManagement 接口指针。该指针在类构造函数中初始化,是 CAAAfrMRURep 类的一个数据成员。
以SelElement为参数调用SelectElement方法,会通知 CAAAfrMRUManager 位于SelElement位置的项已被选中。管理器会对自身列表重新排序,并发送一条通知。所有为此类通知注册了回调的 CAAAfrMRURep 对象,都可以更新自己的菜单项列表。 - 启动命令,在对话框中显示选中的项目
CATAfrStartCommand是一个全局函数,用于启动一个命令标题。该命令标题通过其名称CAAAfrMRUSelElementHdr标识。此命令标题实例已在通用工作台的 Add-in 中创建,关于该命令标题实例的完整说明请见下一节。
...
void CAAAfrMRURep::SelectCB(CATCommand * iPublishingCommand,
CATNotification * iNotification,
CATCommandClientData iData)
{
int SelElement = CATPtrToINT32(iData) + 1;
_pIAfrMRUManagement->SelectElement(SelElement);
CATCommand * pCmd = NULL ;
::CATAfrStartCommand("CAAAfrMRUSelElementHdr",pCmd);
...
实例化 MRU 标题类
...
void CAAAfrGeneralWksAdn::CreateCommands()
{
CATCommandHeader * pHdr = (CATCommandHeader*) new CAAAfrMRUHeader("CAAAfrMRUHdr");
pHdr->SetVisibility(0);
...
MRU 命令标题通过类构造函数创建。
下面两张图说明了使用与不使用 SetVisibility 方法时的区别。

这张图片显示了工具 → 自定义对话框中的命令选项卡。在 “所有命令” 类别下,可以看到 CAAAfrMRUHdr 项被显示出来。之所以会这样,是因为我们在 SetVisibility 代码行前添加了注释(使其失效)。该 MRU 标题不包含多语言(NLS)资源 [10],因此显示的名称为命令标题实例的内部名称(即类构造函数的传入参数)。

现在,通过将 0 作为参数调用 SetVisibility,可以看到在 Bulk Loading... 和 Capture 之间,CAAAfrMRUHdr 已经不再显示。
隐藏命令标题的好处在于:最终用户无法将该命令拖放到工具栏中,也无法通过命令输入框(Power Input)启动它,因为用户不知道其名称。但需要注意:如果知晓命令标题的名称,SetVisibility 方法并不能禁止在命令输入框中启动该命令标题。而对于本 MRU 标题来说,这一点并无影响,因为该 MRU 标题并未关联任何 CATCommand 命令。
...
pHdrMRU = (CATCommandHeader*) new CAAAfrGeneralWksAddinHeader("CAAAfrMRUSelElementHdr",
"CAAAfrGeoCommands",
"CAAAfrMRUSelElementCmd",
(void *) NULL);
pHdrMRU->SetVisibility(0);
...
下一个命令标题实例名称为 CAAAfrMRUSelElementHdr。该标题是一个标准命令标题,因此使用 CAAAfrGeneralWksAddinHeader 命令标题类来创建实例 [3]。
不过,如果你尝试在命令输入框(Power Input)中输入 CAAAfrMRUSelElementHdr,对话框命令仍会被启动,并显示当前选中的项目。

该 MRU 用例中的最后一个命令标题是 CAAAfrMRUAddElementHdr。它是一个标准命令标题,用于启动 CAAAfrMRUAddElementCmd 命令,该命令定义在 CAAApplicationFrame.edu 框架的 CAAAfrGeoCommands 模块中。
...
new CAAAfrGeneralWksAddinHeader("CAAAfrMRUAddElementHdr",
"CAAAfrGeoCommands",
"CAAAfrMRUAddElementCmd",
(void *) NULL);
...
在 CNext/resources/msgcatalog 目录下 [10],你可以在 CAAAfrGeneralWksAddinHeader.CATNls 文件中找到如下语句:
...
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.Category = "File" ;
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.Title = "Add Item in MRU" ;
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.ShortHelp = "Add Item in MRU" ;
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.Help = "Add Item in MRU" ;
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.LongHelp = "Add Item in MRU
This command adds a new item in the MRU list." ;
...
并在 CAAAfrGeneralWksAddinHeader.CATRsc 文件中:
...
CAAAfrGeneralWksAddinHeader.CAAAfrMRUAddElementHdr.Icon.Normal = "I_CAAMRUAddItem" ;
...
其中 I_CAAMRUAddItem 为下图所示图标,该图标文件可在 CNext/resources/graphic/icons/normal 目录下找到。

简要总结
- 该命令标题是一个组件,其对象模型(OM)与 C++ 类均继承自
CATAfrDialogCommandHeader,并实现CATIAfrCommandHeaderRep接口。 - 自定义图形表现由一个必须继承自
CATAfrCommandHeaderRep的类来创建。
References
| [1] | Creating a Combo Command Header |
| [2] | The Command Headers |
| [3] | Making Your Document Independent Command Available in All Workbenches |
| [4] | CATDlgPushItem |
| [5] | Building and Launching a CAA V5 Use Case |
| [6] | Creating Interfaces |
| [7] | The Callback Mechanism (Technical Article) |
| [8] | The Callback Mechanism (Use Case) |
| [9] | The CAA Command Model |
| [10] | Creating Resources for Command Headers |
| [11] | Object Modeler Inheritances |









浙公网安备 33010602011771号