Creating a Most Recent Used Command Header-如何创建一个命令头类,使其呈现为菜单中的动态项列表?

摘要

本用例说明如何创建一个专用命令头。该命令头具备自定义图形呈现形式,其图形呈现为动态菜单项列表,而非菜单中的单个按钮项。
  • 本用例将使您掌握

  • CAAAfrMRUHeader 用例

    • CAAAfrMRUHeader 的功能
    • CAAAfrMRUHeader 的启动方法  
    • CAAAfrMRUHeader 代码所在位置  
  • 分步实现

  • 简要总结

  • 参考资料

 

本用例您将学到的内容

本用例演示如何创建一个自定义命令头。在菜单中,其默认呈现形式为单个按钮项,而本用例实现的图形呈现形式为动态按钮项列表。您将学习如何创建一类命令头,例如文件菜单中用于显示最近使用文档的这类命令头。实现过程分为三个步骤:
  1. 创建代表命令头的组件
    该组件必须继承自 CATAfrDialogCommandHeader 类。
  2. 创建实例化图形呈现形式的组件
    该组件必须继承自 CATAfrCommandHeaderRep 类,并实例化一个或多个 CATDlgPushItem 类对象。
  3. 创建控制图形呈现所用数据的组件
    所用数据为一个字符串列表,该列表与 V5 文档实例无关。这意味着无论当前处于哪个文档,甚至在未打开任何文档的情况下,该字符串列表均保持一致。

 

您也可以阅读 CAAAfrComboColorHeader 用例 [1],该用例介绍了另一种自定义命令头。在该用例中,图形呈现形式为工具栏中的一个组合框。与当前用例不同的是,其数据(当前颜色)与文档相关联。

为充分理解本文内容,建议您先阅读技术文章《命令头》[2],尤其是创建自定义命令头章节。

CAAAfrMRUHeader 用例

CAAAfrMRUHeader 是 CAAApplicationFrame.edu 框架的一个用例,用于演示 ApplicationFrame 框架的各项功能。

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 接口以提供自定义的图形表现形式。该接口包含三个方法:

  • CreateCtxMenuRepCreateToolbarRep:无返回值
  • CreateMenuRep
    它会实例化一个 CAAAfrMRURep 类的实例,该类的说明如下。
    图 3 命令标题图形化表示 UML 图

图片

 

CAAAfrMRURep 类会根据 CAAAfrMRUManager 组件所管理列表的内容,创建一个或多个 CATDlgPushItem [4] 类实例。CAAAfrMRURep 类还会设置一个回调方法,以便在该列表发生变化时收到通知。
MRU 标题在通用工作台的一个外接程序(Add-in)[3] 中被实例化。分步操作部分的最后一步会讲解该实例化过程。

如何启动 CAAAfrMRUHeader

要启动 CAAAfrMRUHeader,需先搭建编译环境,随后编译 CAAAfrMRUHeader、CAAAfrGeneralWksAddin [3] 及其依赖模块,配置运行环境,再执行该用例 [5]。
在启动运行之前,需编辑位于 CAAApplicationFrame.edu 框架字典目录下的接口字典文件 CAAApplicationFrame.edu.dico
  • Windows 系统路径:InstallRootDirectory\CAAApplicationFrame.edu\CNext\code\dictionary\
  • UNIX 系统路径:InstallRootDirectory/CAAApplicationFrame.edu/CNext/code/dictionary/

在该文件中,删除以下两行代码行首的注释符 #

#CAAAfrGeneralWksAddin       CATIWorkbenchAddin          libCAAAfrGeneralWksAddin
#CAAAfrGeneralWksAddin       CATIAfrGeneralWksAddin      libCAAAfrGeneralWksAddin

修改完成后,执行 mkCreateRuntimeView 命令。

CAAAfrMRUHeader 代码位置

CAAAfrMRUHeader 用例由多个类组成,这些类位于 CAAApplicationFrame.edu 框架的四个模块中:
  • Windows 系统路径:InstallRootDirectory\CAAApplicationFrame.edu
  • Unix 系统路径:InstallRootDirectory/CAAApplicationFrame.edu

其中 InstallRootDirectory 为 CAA 安装光盘的安装目录。

CAAAfrCustCommandHdrModel.m 模块

CAAAfrCustCommandHdrModel.m 模块包含用于定义 CAAAfrMRUManager 组件以及该组件所发送通知的相关类。
  • 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(源文件)

分步操作

CAAAfrMRUHeader 包含四个逻辑步骤:
  1. 创建 CAAAfrMRUManager 组件
  2. 创建表示 MRU 命令标题的组件
  3. 创建用于实例化图形表现形式的类
  4. 实例化 MRU 标题类

 

创建 CAAAfrMRUManager 组件

CAAAfrMRUManager 组件用于管理要在菜单中显示的 MRU 列表。该列表通过 CAAIAfrMRUManagement 接口进行管理。本文不介绍该接口的创建说明,更多细节请参考 “创建接口” 用例 [6]。
// 定义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;
};
CAAAfrMRUManager 类继承自 CATBaseUnknown—— 如图 1 所示:
  • 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 命令标题的组件

MRU 标题是一个组件,它必须在对象模型(Object Modeler)层面和 C++ 层面均继承自 CATAfrDialogCommandHeader,并且必须实现 CATIAfrCommandHeaderRep 接口(见图 2)。

组件创建

以下是 CAAAfrMRUHeader 头文件内容:
// 应用框架模块
#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 接口实现

ApplicationFrame 框架的该接口,所有需要自定义图形表现形式的命令标题都必须实现。在图 2 中可以看到,CAAEAfrCommandHeaderRepForMRU 类是为 CAAAfrMRUHeader 组件实现该接口的类。
以下是 CAAEAfrCommandHeaderRepForMRU 头文件内容:
...
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 和 CreateCtxMenuRepCATIAfrCommandHeaderRep 接口的方法。

以下是 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 标题不在工具栏和右键菜单中显示,因此 CreateToolbarRepCreateCtxMenuRep 均返回 E_FAIL。

创建实例化图形表现的类

该类为 CAAAfrMRURep 类,其主要作用是:
  • 设置回调,以便在 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 源文件内容如下:

类的构造函数

首先,对存储所有 CATDlgPushItem 类实例指针的数据成员 _pItemList 进行初始化。
...
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);
   }
}
AddCallback 是一个静态全局函数,其参数说明如下:
  • 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 方法

您必须重写此方法。该方法的作用是创建并初始化图形界面表现。具体工作由 ModifyListItem 方法完成。
...
HRESULT  CAAAfrMRUoRep::Build()
{
 ModifyListItem() ;
}
...

ModifiedCB 方法

ModifiedCB 方法会在 CAAAfrMRUManager 组件发送 CAAAfrMRUManagerNotification 通知时被调用。它用于告知 CAAAfrMRURep 类实例:要么是添加了新项,要么是某个元素被选中。无论哪种情况,具体处理均由 ModifyListItem 方法完成。
...
void CAAAfrMRURep::ModifiedCB(CATCallback, 
                              void *, 
                              CATNotification * iNotification,
                              CATCallbackEvent, 
                              CATSubscriberData)
{
   ModifyListItem();
}
...

ModifyListItem 方法

该方法可由 Build 方法调用(此时尚未创建任何按钮项),也可由 ModifiedCB 方法调用(此时部分按钮项可能已存在)。该方法的主要工作为:
  • 从 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 方法

当最终用户在显示的菜单项中选择某一项时,将调用 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 标题类

MRU 标题与文档无关,因此该标题在 ** 通用工作台的插件(Add-in)** 中进行实例化 [3]。以下是 CAAAfrGeneralWksAdn 类的代码片段,该类实现了 CATIAfrGeneralWksAddin 接口。
...
void CAAAfrGeneralWksAdn::CreateCommands()
{
  CATCommandHeader * pHdr = (CATCommandHeader*) new CAAAfrMRUHeader("CAAAfrMRUHdr");
  pHdr->SetVisibility(0);
...

MRU 命令标题通过类构造函数创建。

在使用过程中,SetVisibility 方法是最为关键的部分。该方法属于 CATCommandHeader 类,用于在 工具 / 自定义 命令中隐藏此命令标题实例。
执行该命令会弹出一个包含多个选项卡的对话框,其中 **“命令” 选项卡 ** 会按类别显示所有 “可见” 的命令标题实例。

下面两张图说明了使用与不使用 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]。

该实例会启动 CAAAfrMRUSelElementCmd 命令,该命令定义在 CAAApplicationFrame.edu 框架的 CAAAfrGeoCommands 模块中。
该命令标题实例同样在工具 / 自定义界面中被隐藏,因为本示例只希望在最终用户从文件菜单中选择某一项时才启动此命令。

不过,如果你尝试在命令输入框(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 的类来创建。
数据模型在整个会话期间由唯一实例维护。
posted @ 2026-04-22 14:44  Breadss  阅读(1)  评论(0)    收藏  举报