MSCE C++官网一步步学习搬运7
第七章、给MDL应用添加用户界面
我们在本系列的前一章实现了动态放置B样条曲线的功能。现在要给这个工具添加如下图形用户界面(GUI):
增加这个界面后能帮助我们在放置曲面时指定曲面的大小和其所在的层。
在nativeCode(本机代码)应用程序中,可以通过MDL资源(.r文件)或MFC资源(.rc文件)来添加图形用户界面。前者是Bentley应用程序所特有的,后者是微软的实现方式。虽然MDL资源不支持可视化设计,但它是通过定义数据而不用写程序代码来支持用户交互的,这一点上反而比较简单。再者,您可以将MicroStation中已有的对话条目(即俗称的控件)集成到自己的对话框中,这也是MDL资源的另一大好处。总之,MDL资源在写nativeCode程序时占有极其重要的地位,Bentley的内部代码绝大多数都是通过MDL资源来写的界面,绝少是用MFC资源写出来的(有一些是用最新的WinForm或WPF写出来的)。有关MDL资源的详细信息请参阅MicroStationApi.chm中的“Dialog Box Manager Overview(对话框管理器概述)”、“Standard dialog items(标准对话条目)”和“Hook Functions(钩函数)”等内容。
在一篇文章中描述MDL资源的每一个细节几乎是不可能的,这个任务也不是本系列文章的要点。所以,在本例中我们将集中于以上两个对话条目。为了实现这个界面,我们要做一些设计方面的考虑:需要给每个对话框和对话条目赋一个标识(ID)以便在资源和代码中更容易地使用和引用;还需要给大多数对话条目指定“存取串(access string)”以便快速而直接地访问它们所引用的值。在本例中,所定义的条目标识和存取串如下图所示:
1、在HelloWorld.h中增加如下定义内容:
#include <Bentley\Bentley.r.h> #define ItemList_BsplineSurface 1 #define TEXTID_BaseArcRadius 1 #define COMBOBOXID_MyLevel 1 #define HOOKITEMID_MyLevelCombo 1 typedef struct helloworldinfo { double baseArcRadius; Utf16Char levelName[512]; }HelloWorldInfo;
- 对于同一种资源,各项的ID值必须唯一。不同资源类型的项的ID值不必唯一;
- 组合框条目COMBOBOXID_MyLevel用来显示当前设计文件中的所有有效的层。组合框条目不同于一般的条目,它没有默认的标准动作,因此需要为其添加一个钩函数,这就是为什么还要定义HOOKITEMID_MyLevelCombo的原因;
- 通常情况下,所有的存取串都定义在一个结构体中并在.mt和.cpp文件中发布它;
- 注意CE中对ItemList的定义,它会出现的随后的r和普通的.r中,用来关联命令和工具设置对话框。
2、在HelloWorldCmd.r中修改CT_CREATE定义中的最后一项,增加ItemList_BsplineSurface如下:
{ 4, CT_NONE, INHERIT, NONE, "BsplineSurface", CMDNAME_PlaceBsSurfaceTool, ItemList_BsplineSurface},
3、在HelloWorld.r中添加如下内容:
#include <Mstn\MdlApi\rscdefs.r.h> #include <Mstn\MdlApi\dlogbox.r.h> #include "HelloWorld.h" …… CmdItemListRsc ItemList_BsplineSurface = {{ {{10*XC, YC/2, 15*XC, 0}, Text, TEXTID_BaseArcRadius, ON, 0, "", ""}, {{10*XC, YC*2, 20*XC, 0}, ComboBox, COMBOBOXID_MyLevel, ON, 0, "", ""}, }}; DItem_TextRsc TEXTID_BaseArcRadius = { NOCMD, LCMD, NOSYNONYM, NOHELP, LHELP, NOHOOK, NOARG, 15, "%w", "%w", "0.0", "", NOMASK, TEXT_NOCONCAT, "基准弧半径(~B):", "g_helloWorld.baseArcRadius" }; DItem_ComboBoxRsc COMBOBOXID_MyLevel = { NOCMD, LCMD, NOSYNONYM, NOHELP, MHELP, HOOKITEMID_MyLevelCombo, NOARG, 512, "%s", "%s", "", "", NOMASK, 0, 18, 4, 0, 0, COMBOATTR_READONLY | COMBOATTR_FULLWIDTH | COMBOATTR_SORT, " 放置层(~L):", "g_helloWorld.levelName", { {0, 512, ALIGN_LEFT, ""}, } };
- 命令CMD_HELLOWORLD_CREATE_BSPLINESURFACE对应ItemList_BsplineSurface,而ItemList_BsplineSurface中定义了两个条目列表。当一个命令被处理并成为当前命令时,MicroStation会查找该命令关联的CmdItemListRsc资源并将其内容显示在工具设置(Tool Settings)对话框中;
- 我们为“基准弧半径”定义了一个文字条目资源。其中的15是控制在该文字条目中最大可输入的字符个数。“%w”表示该文字条目中的值将以工作单位格式显示。(~B)中的波折号表示B字符会带有下划线——这就是我们通常说的助记符。当工具设置对话框聚焦的情况下按Alt+B组合键就能选中该文字条目。g_helloWorld.baseArcRadius是该文字条目的存取串。我们可通过该变量获取或设置这个文字条目的值。
- 同理,我们为“放置层”定义了组合框条目资源。组合框条目内容往往需要通过代码来赋值,为此在资源文件中定义了一个钩条目ID(HOOKITEMID_MyLevelCombo)与该条目相关联。将来在.cpp文件中用一个钩函数对应该钩条目ID。从而当用户操作该条目时能调用到cpp中的钩函数。
4、如下建立一个MicroStation类型文件HelloWorldTyp.mt:
#include "HelloWorld.h" publishStructures (helloworldinfo);
回顾一下第二章中看到的那个生成ma+dll的流程图,在图的右上角显示的就是在.mt文件中包含.h头文件,.mt文件会被生成.r文件,.r文件又进一步生成.rsc,最后合并到.ma文件中。
5、如下修改HelloWorld.mke文件:
- 将编译后的资源HelloWorldTyp.rsc添加到宏定义appRscs中:
appRscs = $(o)$(appName).rsc $(o)$(appName)cmd.rsc $(o)$(appName)typ.rsc
将如下四行(注意有两个重要的空行)添加到Generate resource files一节后面。这四行就是从HelloWorldTyp.mt生成HelloWorldTyp.r,从HelloWorldTyp.r生成HelloWorldTyp.rsc:
$(o)$(appName)typ.r : $(baseDir)$(appName)typ.mt $(o)$(appName)typ.rsc : $(o)$(appName)typ.r
6、对HelloWorld.cpp做多出修改:
- 确保包含如下几个头文件:
#include <Mstn\MdlApi\MdlApi.h> #include <DgnPlatform\DgnPlatformApi.h> #include <DgnView\DgnElementSetTool.h> #include <Mstn\ISessionMgr.h> #include "HelloWorldCmd.h" #include "HelloWorld.h"
- 声明一个全局变量:
HelloWorldInfo g_helloWorld;
修改类PlaceBsSurfaceTool中的成员函数CreateBsSurface和_OnDataButton以响应工具设置中的两个参数如下。基准弧半径直接是界面中取得,当要将B样条曲面添加到模型前取得界面中用户选择的层,从层名得到对应的层Id,设置到EditElementHandle上。
bool CreateBsSurface(EditElementHandleR eeh, DPoint3dCP pBasePt) { MSBsplineSurface bsSurface; MSBsplineCurve bsCurves[4]; DPoint3d center[4]; RotMatrix rMatrix[4]; double radius = g_helloWorld.baseArcRadius; …… } …… virtual bool _OnDataButton(DgnButtonEventCR ev) override { EditElementHandle eeh; if (CreateBsSurface(eeh, ev.GetPoint())) { WString wStr(g_helloWorld.levelName); FileLevelCacheR levelCache = ISessionMgr::GetActiveDgnFile()->GetLevelCacheR(); LevelHandle levelHandle = levelCache.GetLevelByName(wStr.GetWCharCP()); ElementPropertiesSetterPtr setter = ElementPropertiesSetter::Create(); setter->SetLevel(levelHandle.GetLevelId()); setter->Apply(eeh); eeh.AddToModel(); } _OnReinitialize(); return true; }
增加一个条目钩函数,该函数遍历当前设计文件中有效的层并将它们赋给组合框条目MyLevel:
void myLevel_comboHook (DialogItemMessage *dimP) { RawItemHdr *riP = dimP->dialogItemP->rawItemP; dimP->msgUnderstood = TRUE; switch (dimP->messageType) { case DITEM_MESSAGE_CREATE: { ListModelP pListModel = mdlListModel_create(1); DgnFileP pDgnFile = ISessionMgr::GetActiveDgnFile(); for (LevelHandle lh : pDgnFile->GetLevelCacheR()) { WString lvlName; lh.GetDisplayName(lvlName); mdlListModel_insertString (pListModel, lvlName.GetWCharCP(), -1); } mdlDialog_comboBoxSetListModelP(riP, pListModel); break; } case DITEM_MESSAGE_DESTROY: { ListModelP pListModel = mdlDialog_comboBoxGetListModelP (riP); mdlListModel_destroy (pListModel,TRUE); break; } default: dimP->msgUnderstood = FALSE; break; } }
定义一个全局动态数组uHook[],并在MdlMain函数中发布它。
DialogHookInfo uHooks[] = { {HOOKITEMID_MyLevelCombo, (PFDialogHook)myLevel_comboHook }, }; …… …… mdlDialog_hookPublish (sizeof(uHooks)/sizeof(DialogHookInfo), uHooks);
- 在MdlMain函数中发布helloWorld结构并初始化其值:
SymbolSet *setP = mdlCExpression_initializeSet (VISIBILITY_DIALOG_BOX, 0, 0); mdlDialog_publishComplexVariable (setP, "helloworldinfo", "g_helloWorld", &g_helloWorld); g_helloWorld.baseArcRadius = 2000; strcpy_s (g_helloWorld.levelName, 512, "Default");
7、在Visual Studio中保存所有文件,回到编译黑窗口键入bmake -a重生成该项目,然后切换到MicroStation来测试。当您装载HelloWorld应用并键入HELLOWORLD CREATE BSPLINESURFACE,您将会看到本章开头显示的第一个图片。下图是放置了三个曲面的例子,每个曲面都在不同的层上且具有不同的基本弧半径。