Creating a Document's Window - Part 2-如何创建多视图窗口

摘要

本文介绍如何创建用于显示文档的窗口,并详细说明多视图窗口的专属特性。此外,《创建文档窗口 - 第一部分》[1] 中的用例,可帮助读者了解如何通过命令启动文档窗口。

通过本用例你将掌握的内容

  • CAAAfrCustomWindow 用例介绍
    • CAAAfrCustomWindow 功能说明
    • CAAAfrCustomWindow 启动方式
    • CAAAfrCustomWindow 源码路径
  • 操作步骤详解
  • 内容小结
  • 参考文献

本用例学习目标

本用例旨在演示,如何为特定类型的文档创建包含多视图的窗口 [2]。

CAAAfrCustomWindow 用例

CAAAfrCustomWindow 是 CAAApplicationFrame.edu 框架下的示例用例,用于演示应用框架(ApplicationFrame)的各项功能特性。

 

CAAAfrCustomWindow 功能说明

CAAAfrCustomWindow 用例用于为 CAASample 类型文档创建文档窗口。

图片

 启动 CAAAfrCustomWindow 方法

如需了解本用例的详细启动步骤,请参阅《CAAGeometry 示例》用例中 **「CAAGeometry 用例启动方法」** 章节。
针对本用例的专属操作要求如下:
请勿在命令行输入模块名称,改为输入 CNEXT。应用程序启动就绪后,执行以下操作:
  1. 选择 文件 → 新建(File->New)
  2. 在新建弹窗中选中 CAASample,点击 确定(OK)

CAAAfrCustomWindow 代码文件位置

CAAAfrCustomWindow 用例仅包含一个类:CAAAfrCustomWindow,该类位于 CAAApplicationFrame.edu 框架的 CAAAfrGeoWindows.m 模块中:
Windows 系统
安装根目录\CAAApplicationFrame.edu\CAAAfrGeoWindows.m\
Unix 系统

安装根目录/CAAApplicationFrame.edu/CAAAfrGeoWindows.m/

其中,安装根目录 为 CAA 安装包的安装路径。

该用例包含两个核心文件:
  • 头文件 CAAAfrCustomWindow.h:存放于 CAAApplicationFrame.edu 框架的 PrivateInterfaces 目录
  • 源文件 CAAAfrCustomWindow.cpp:存放于 src 目录

分步实现

创建自定义窗口共分为五个步骤:
序号步骤所在位置
1 创建自定义窗口类 本地接口目录 与 源码目录
2 定义对话框对象行为 Build 方法
3 定义 MDI 文档窗口行为 Build 方法
4 窗口复制 Duplicate 方法
5 窗口销毁 DeleteWindow 方法 与 析构函数
 
 

创建自定义窗口类

本文档所实现的文档窗口,在窗口上部包含两个尺寸不同的 3D 视图,窗口下部包含一个2D 视图
该窗口对应的类为 CAAAfrCustomWindow,其头文件定义如下:
class CAAAfrCustomWindow: public CATFrmWindow
{
  public:
    CAAAfrCustomWindow(const CATString & iName, CATFrmEditor * iEditor=NULL);
    virtual ~CAAAfrCustomWindow();
    
    virtual CATFrmWindow * DuplicateWindow();
    virtual void DeleteWindow();
    
    void Build();
    
    void GetViewers(CATNavigation3DViewer ** oV1,
                       CATNavigation3DViewer ** oV2, 
                       CATNavigation2DViewer ** oV3);
   ...
  private:
    CATNavigation3DViewer * _pViewer1;
    CATNavigation3DViewer * _pViewer2;
    CATNavigation2DViewer * _pViewer3;
    CATPathElement        * _pRootObjectPath;
};
CAAAfrCustomWindow 包含以下成员:
  1. 构造函数与析构函数;
  2. DuplicateWindow 方法:供窗口菜单栏的「新建窗口」命令调用;
  3. DeleteWindow 方法:用户关闭窗口或关闭文档时自动调用;
  4. Build 方法:所有对话框类均具备该方法;
  5. GetViewers 方法:用于获取三个视图的指针。
类的数据成员包含:三个导航视图对象,以及用于展示的模型根节点对象。

该类的构造函数实现逻辑简洁。

// 构造函数:初始化自定义窗口
CAAAfrCustomWindow::CAAAfrCustomWindow(const CATString & iName,
                                       CATFrmEditor    * iEditor)
                  : CATFrmWindow(iName, iEditor),          // 调用父类构造函数
                    _pViewer1(NULL), _pViewer2(NULL), _pViewer3(NULL),  // 初始化3个视图指针为空
                    _pRootObjectPath(NULL)                // 初始化根对象路径指针为空
{
}

实现界面对象行为

界面对象的行为主要包括:
  • 实例化所需的各类显示视图
  • 在文档窗口中排布这些视图
  • 设置当前激活视图
构造函数会完成视图创建、窗口内布局排布,并指定其中一个视图为当前视图。

实例化各类显示视图

void CAAAfrCustomWindow::Build()
{
  int width, height;
  CATString  ViewerName; 

  // 获取视图父级框架窗口
  CATDlgFrame * FrameWindow = GetViewerFrame() ;
  CATString FrameName = "FrameName";
  // 创建自定义对话框容器框架,启用网格布局、无边框样式
  CATDlgFrame * Frame = new CATDlgFrame(FrameWindow, FrameName,
                                        CATDlgGridLayout|CATDlgFraNoFrame);

  // 创建一号3D导航视图
  ViewerName = "Viewer3Dnum1";
  width = 200 ; height = 200;
  _pViewer1 = new CATNavigation3DViewer(Frame, ViewerName, width, height);
    
  // 创建二号3D导航视图
  ViewerName = "Viewer3Dnum2" ;
  width = 200 ; height = 300 ;
  _pViewer2 = new CATNavigation3DViewer(Frame, ViewerName, width, height);

  // 创建2D导航视图
  ViewerName = "Viewer2D" ;
  width = 200 ; height = 500 ;
  _pViewer3 = new CATNavigation2DViewer(Frame, ViewerName, width, height);
...   
}

视图尺寸单位为像素。本例中名为 Frame 的多文档应用程序窗口,是所有视图的父容器。该容器框架专门创建于:CATFrmWindow 原生框架(FrameWindow)与各类视图之间。借助该容器,我们可以使用网格布局排布视图,而非实现更为复杂的标签布局[3]。原生 FrameWindow 框架并未采用 CATDlgGridLayout(网格布局)样式 [1]。

在文档窗口中排布视图

...
  // 将自定义框架四边绑定至父级窗口
  FrameWindow->Attach4Sides(Frame);
  // 设置3D视图1网格布局约束
  _pViewer1->SetGridConstraints(0,0,1,1,CATGRID_4SIDES);
  // 设置3D视图2网格布局约束
  _pViewer2->SetGridConstraints(0,1,1,1,CATGRID_4SIDES);
  // 设置2D视图网格布局约束
  _pViewer3->SetGridConstraints(1,0,2,1,CATGRID_4SIDES);
  // 设置网格行、列支持自适应拉伸
  Frame->SetGridRowResizable(0,1);
  Frame->SetGridRowResizable(1,1);
  Frame->SetGridColumnResizable(0,1);
  Frame->SetGridColumnResizable(1,1);
...      

Attach4Sides 方法用于将自定义框架对象四边贴合绑定至父级 FrameWindow 窗口。该方法为标签布局的专用接口。

随后,作用于 _Viewer1SetGridConstraints 方法,将该视图放置在第 0 行、第 0 列交叉的网格单元格内,横向跨越 1 行、纵向跨越 1 列。

CATGRID_4SIDES 表示视图铺满所在单元格的全部区域。
在各类 SetGridxxResizable 方法中,以第 0 行、第 1 行及第 0 列、第 1 列作为入参;由于第二个参数赋值为 1,代表上述行列均设为可拉伸

反之,参数为 0 则代表不可拉伸

图片

 设置当前视图

...
  _pViewer1->Reframe();
  SetViewer(_pViewer1);
...

Reframe 方法用于完整显示 _pViewer1 视图中的全部内容;SetViewer 方法则将 _pViewer1 设置为当前激活视图

后续可通过 CATFrmWindow 类的 GetViewer 方法获取该当前视图。

至此,自定义文档窗口已创建完成,但目前仅具备基础界面,缺少交互功能。接下来我们就来配置各类交互机制。

实现 MDI 文档窗口行为

MDI 文档窗口的行为主要包含以下内容:
  1. 获取可视化管理器与文档根对象;
  2. 要求文档创建自身图形表达,并将其挂载至可视化管理器;
  3. 维护上述对象与所有交互相关对象之间的关联关系。

 

获取可视化管理器与文档根对象

同样在 Build 方法中,我们现在让全局唯一的可视化管理器知晓需要显示的文档,以及对应的观察视角。通过 CATFrmWindow 类的 GetEditor 方法,你可以获取构造类对应的编辑器。
 CATFrmEditor * pEditor = GetEditor();
  CATVisManager * pVisuManager = CATVisManager::GetVisManager();

  // 此处为获取根模型 pRootObject 的代码

本文中不会讲解获取待显示模型的代码,因为该代码对于真实的 V5 文档并无实际用途。

通知文档创建图形显示对象,并将其挂载到可视化管理器

 if ( (NULL != pEditor  ) && (NULL != pRootObject ) && (NULL != pVisuManager) )
    { 
      _pRootObjectPath = new CATPathElement(pRootObject);

      CATCommand * CommandSelector = (CATCommand*) pEditor->GetCommandSelector();

      list<IID> ListIVisu3d, ListIVisu2d;
      IID visu3d = IID_CATI3DGeoVisu;
      IID visu2d = IID_CATI2DGeoVisu;
      ListIVisu3d.fastadd(&visu3d);
      ListIVisu2d.fastadd(&visu2d);
     
      CATViewpoint * pViewPoint1 = NULL;
      CATViewpoint * pViewPoint2 = NULL;
      CATViewpoint * pViewPoint3 = NULL;

      pViewPoint1 = (CATViewpoint*) &(_pViewer1->GetMain3DViewpoint());
      pVisuManager->AttachTo(_pRootObjectPath,pViewPoint1,ListIVisu3d,CommandSelector);

      pViewPoint2 = (CATViewpoint*) &(_pViewer2->GetMain3DViewpoint());
      pVisuManager->AttachTo(_pRootObjectPath,pViewPoint2,ListIVisu3d,CommandSelector);

      pViewPoint3 = (CATViewpoint*) &(_pViewer3->GetMain2DViewpoint());
      pVisuManager->AttachTo(_pRootObjectPath,pViewPoint3,ListIVisu2d,CommandSelector);

如果成功获取到编辑器、可视化管理器和根对象,就会将根对象封装为 CATPathElement 对象,同时获取命令选择器(即位于命令树顶端的命令 [4]),并创建文档对象所实现的 2D 和 3D 接口列表。随后,为每个视图获取其主观察视角,并将文档根对象接口列表命令选择器一并挂载到可视化管理器。

管理这些对象与所有交互功能对象之间的关联

   CATPSO * pPSO = pEditor->GetPSO() ;  // 预选择对象集
      CATHSO * pHSO = pEditor->GetHSO() ;  // 高亮显示对象集

      pVisuManager->AttachPSOTo(pPSO, pViewPoint1);
      pVisuManager->AttachPSOTo(pPSO, pViewPoint2);
      pVisuManager->AttachPSOTo(pPSO, pViewPoint3);
      pVisuManager->AttachHSOTo(pHSO, pViewPoint1);
      pVisuManager->AttachHSOTo(pHSO, pViewPoint2);
      pVisuManager->AttachHSOTo(pHSO, pViewPoint3);

      CATISO * pISO = pEditor->GetISO(); 
      pISO->AddViewer(_pViewer1);
      pISO->AddViewer(_pViewer2);
      pISO->AddViewer(_pViewer3);
    }
  }
}

这段代码从编辑器中获取预选择对象集(PSO)高亮显示对象集(HSO),并通知可视化控制器将对应的观察视角与 PSO、HSO 进行绑定。

现在,当最终用户将鼠标移动到视图中的某个图形对象上方时,该图形对应的路径元素会被放入 PSO,同时高亮显示并加入 HSO。
同时还会获取交互对象集(ISO)。这类对象不属于文档本身,但在视图中操作图形时非常实用,例如用于移动或变形的操控手柄。此时,ISO 已关联三个视图。

至此,文档窗口的 Build 方法已全部实现完成。

复制文档窗口

该功能通过复制(Duplicate)方法实现。
...
CATFrmWindow * CAAAfrCustomWindow :: DuplicateWindow()
{
  CATString NameOfThis = GetBaseName().CastToCharPtr();
  CAAAfrCustomWindow * pWindowToReturn = NULL;
  pWindowToReturn = new CAAAfrCustomWindow(NameOfThis,GetEditor());
  pWindowToReturn->Build();
  
  pWindowToReturn->SetBaseName(GetBaseName());

  float r,v,b ;
  if (NULL != _pViewer1) _pViewer1->GetBackgroundColor(&r, &v, &b);

  CATNavigation3DViewer * pV1 = NULL;
  CATNavigation3DViewer * pV2 = NULL;
  CATNavigation2DViewer * pV3 = NULL;

  pWindowToReturn->GetViewers(&pV1, &pV2, &pV3);
  if (NULL != pV1) pV1->SetBackgroundColor(r, v, b);

  return  pWindowToReturn;
}
...
复制方法现已实现完成,它执行以下操作:
  • 创建一个与当前窗口类型完全相同的新窗口
  • 设置与当前窗口相同的基础名称。如果仅存在一个窗口,SetBaseName 方法会修改该窗口名称;在基础名称后,会自动追加一个数字序号 [2]
  • 以 RGB 三元组格式获取第一个视图的背景色
  • 获取新创建窗口中的所有视图对象
  • 将获取到的背景色,应用到新窗口的第一个视图上

该方法使用下方的 GetViewers 方法:

void CAAAfrCustomWindow :: GetViewers(CATNavigation3DViewer ** oV1,
                                      CATNavigation3DViewer ** oV2, 
                                      CATNavigation2DViewer ** oV3)
{
  *oV1 = _pViewer1;
  *oV2 = _pViewer2;
  *oV3 = _pViewer3;
}
此方法用于向外输出当前窗口的三个视图指针
  • 将第一个 3D 视图 _pViewer1 赋值给输出参数 oV1
  • 将第二个 3D 视图 _pViewer2 赋值给输出参数 oV2
  • 将 2D 视图 _pViewer3 赋值给输出参数 oV3

窗口销毁

void CAAAfrCustomWindow::DeleteWindow()
{
  if ( (NULL != GetEditor()) && (NULL != _pViewer1) &&
       (NULL != _pViewer2)    && (NULL != _pViewer3) )
  {
    // 从交互对象集(ISO)中解绑视图
    CATISO *pISO = NULL;
    pISO = GetEditor()->GetISO();
    if (NULL != pISO) pISO->RemoveViewer(_pViewer1);
    if (NULL != pISO) pISO->RemoveViewer(_pViewer2);
    if (NULL != pISO) pISO->RemoveViewer(_pViewer3);

    // 获取各视图的观察视点
    CATViewpoint * pViewpoint1 = NULL;
    CATViewpoint * pViewpoint2 = NULL;
    CATViewpoint * pViewpoint3 = NULL;
    pViewpoint1 = (CATViewPoint *) &(_pViewer1->GetMain3DViewpoint());
    pViewpoint2 = (CATViewPoint *) &(_pViewer2->GetMain3DViewpoint());
    pViewpoint3 = (CATViewPoint *) &(_pViewer3->GetMain2DViewpoint());

    // 获取全局唯一可视化管理器
    CATVisManager *pVisuManager = CATVisManager::GetVisManager();
    if ( (NULL != pViewpoint1) &&
         (NULL != pViewpoint2) &&
         (NULL != pViewpoint3) && (NULL != pVisuManager) )
    {
      // 从预选择对象集、高亮对象集中解绑视点
      pVisuManager->DetachPSOFrom(pViewpoint1);
      pVisuManager->DetachPSOFrom(pViewpoint2);
      pVisuManager->DetachPSOFrom(pViewpoint3);
      pVisuManager->DetachHSOFrom(pViewpoint1);
      pVisuManager->DetachHSOFrom(pViewpoint2);
      pVisuManager->DetachHSOFrom(pViewpoint3);

      // 从可视化管理器中解绑视点与根对象路径
      pVisuManager->DetachFrom(_pRootObjectPath, pViewpoint1);
      pVisuManager->DetachFrom(_pRootObjectPath, pViewpoint2);
      pVisuManager->DetachFrom(_pRootObjectPath, pViewpoint3);
    }
  }
  // 调用父类窗口销毁方法
  CATFrmWindow::DeleteWindow();
}

// 析构函数
CAAAfrCustomWindow::~CAAAfrCustomWindow()
{
  // 释放根对象路径内存
  if (NULL != _pRootObjectPath) delete _pRootObjectPath;
  _pRootObjectPath = NULL;
}

文档窗口通过 DeleteWindow 方法完成销毁:

该方法先将所有视图从交互对象集(ISO)中移除,再获取各个视图的观察视点,将视点与预选择对象集(PSO)、高亮对象集(HSO)解除绑定,最后从可视化管理器中解绑视图视点与文档根对象路径,并逐层调用父类的 DeleteWindow 方法。

析构函数仅负责释放根对象路径资源并置空指针。无需手动释放视图对象,因为视图属于界面对象,会在窗口销毁时由框架自动回收。

简要总结

本用例讲解了文档窗口类的整体结构:
  1. Build 方法
    • 在窗口中创建并布局多个视图;
    • 将文档根对象与交互对象集绑定至命令树。
  2. DuplicateWindow 方法:基于现有窗口实例,新建一个窗口副本。
  3. DeleteWindow 方法:将文档根对象与交互对象集从命令树中解除绑定。

References

[1] Creating a Document's Window - Part 1
[2] Understanding the Application Frame Layout
[3] Arranging Dialog Objects
[4] The CAA Command Model
posted @ 2026-04-29 10:16  Breadss  阅读(13)  评论(0)    收藏  举报