MSCE C++官网一步步学习搬运6

第六章、用DgnTool类实现交互式工具

 上一章通过键入命令可实现放置各种图形,但这些图形的位置都是固定的。那么,能否在输入命令后让图形跟着光标走,定位后在该点放置图形呢?答案无疑是肯定的。

在面向对象的MicroStation C++API推出之前,MDL C API中有mdlState_xxx类函数可以实现与MicroStation的交互。在新的C++API中,提供了功能强大的DgnTool类来实现类似的功能。新旧API对照如下:

 新的C++ API都是以类的形式提供的,上表中重要的三个类都派生于DgnTool基类,在派生树中还涉及到其他一些重要的类和接口。如下图所示:

这些类中含有丰富的的成员函数,有些是设置一些参数的,如_AllowSelection;有些是启动一个动作的,如_BeginDynamics;还有一些是处理用户事件的,如_OnDataButton。限于篇幅,我们不在此列出这些类中的所有成员函数了,请您参考MicroStationAPI.chm文档了解它们的详细内容。

要实现一个交互式工具,我们需要定义一个派生于DgnViewTool、DgnPrimitiveTool或DgnElementSetTool的类。然后在命令处理函数中创建该类的一个实例并通过调用InstallTool()函数启动工具。

下面我们一步步将HelloWorld Create BsplineSurface命令改造成一个具有交互能力的工具。

1、在Visual Studio中打开HelloWorld项目,打开HelloWorld.cpp文件,在头部增加头文件DgnView\DgnElementSetTool.h,增加一个派生于DgnPrimitiveTool的类PlaceBsSurfaceTool并实现其中的功能。完成的最终代码如下:

#include <Mstn\MdlApi\MdlApi.h>
#include <DgnPlatform\DgnPlatformApi.h>
#include <DgnView\DgnElementSetTool.h>
#include "HelloWorldCmd.h"

USING_NAMESPACE_BENTLEY_DGNPLATFORM
USING_NAMESPACE_BENTLEY_MSTNPLATFORM
USING_NAMESPACE_BENTLEY_MSTNPLATFORM_ELEMENT

double g_1mu;

struct PlaceBsSurfaceTool : DgnPrimitiveTool
{
	PlaceBsSurfaceTool(int toolId, int promptId) : DgnPrimitiveTool(toolId, promptId) {}

	bool CreateBsSurface(EditElementHandleR eeh, DPoint3dCP pBasePt)
	{
		MSBsplineSurface bsSurface;
		MSBsplineCurve   bsCurves[4];
		DPoint3d         center[4];
		RotMatrix        rMatrix[4];
		double           radius = g_1mu / 2;

		center[0] = center[1] = center[2] = center[3] = *pBasePt;
		center[0].x += radius;
		center[1].x += g_1mu;     center[1].y += radius;
		center[2].x += radius;    center[2].y += g_1mu;
		center[3].y += radius;

		DVec3d xVec = DVec3d::From(1, 0, 0), negativeXVec = DVec3d::From(-1, 0, 0);
		DVec3d yVec = DVec3d::From(0, 1, 0), negativeYVec = DVec3d::From(0, -1, 0);
		DVec3d zVec = DVec3d::From(0, 0, 1);
		rMatrix[0].InitFrom2Vectors(xVec, zVec);  //Front View
		rMatrix[1].InitFrom2Vectors(yVec, zVec);  //Right View
		rMatrix[2].InitFrom2Vectors(negativeXVec, zVec);  //Back View
		rMatrix[3].InitFrom2Vectors(negativeYVec, zVec);  //Left View

		for (int i = 0; i<4; i++)
		{
			bsCurves[i].InitEllipticArc(center[i], radius, radius, 0, PI, &rMatrix[i]);
		}
		if (SUCCESS != mdlBspline_coonsPatch(&bsSurface, bsCurves))
		{
			for (int i = 0; i<4; i++)
				mdlBspline_freeCurve(&bsCurves[i]);
			return false;
		}
		DraftingElementSchema::ToElement(eeh, bsSurface, nullptr, *ACTIVEMODEL);
		mdlBspline_freeSurface(&bsSurface);
		for (int i = 0; i<4; i++)
			mdlBspline_freeCurve(&bsCurves[i]);
		return true;
	}

	virtual void _OnPostInstall() override
	{
		_BeginDynamics();
		AccuSnap::GetInstance().EnableSnap(true);
		__super::_OnPostInstall();
	}

	virtual void _OnRestartTool() override
	{
		PlaceBsSurfaceTool *pTool = new PlaceBsSurfaceTool(GetToolId(), GetToolPrompt());
		pTool->InstallTool();
	}

	virtual void _OnDynamicFrame(DgnButtonEventCR ev) override
	{
		EditElementHandle eeh;
		if (!CreateBsSurface(eeh, ev.GetPoint()))
			return;

		RedrawElems redrawElems;
		redrawElems.SetDynamicsViews(IViewManager::GetActiveViewSet(), ev.GetViewport());
		redrawElems.SetDrawMode(DRAW_MODE_TempDraw);
		redrawElems.SetDrawPurpose(DrawPurpose::Dynamics);
		redrawElems.DoRedraw(eeh);
	}

	virtual bool _OnDataButton(DgnButtonEventCR ev) override
	{
		EditElementHandle  eeh;
		if (CreateBsSurface(eeh, ev.GetPoint()))
			eeh.AddToModel();
		_OnReinitialize();
		return true;
	}

	virtual bool _OnResetButton(DgnButtonEventCR ev) override
	{
		_EndDynamics();
		_ExitTool();
		return true;
	}
};

 下面我们逐点解释以上代码的含义:

  • 包含头文件DgnView\DgnElementSetTool.h使得RedrawElems对象可用;
  • 由于我们要实现一个放置工具,所以定义了一个派生于DgnPrimitiveTool的类PlaceBsSurfaceTool;
  • 构造函数PlaceBsSurfaceTool具有两个参数:工具标识和工具提示标识。您可能会疑惑,为何这里提供的是两个整数型参数,其实这两个整数值对应到资源文件的消息列表中的具体字符串。之所以要这样设计,完全是为了方便软件的国际化。当要想将软件改成另外一种语言时,仅仅需要改资源文件即可,源代码文件不需要做任何修改。本章随后的内容还会详细解释资源文件的细节;
  • CreateBsSurface是类PlaceBsSurfaceTool特有的成员函数,它几乎是前一章createABsplineSurface函数的翻版,但没有直接将生成的元素句柄eeh添加到模型中而是作为参数返回;
  • 重写了继承来的_OnPostInstall函数。该函数会在工具加载后执行。在这个函数中调用了成员函数_BeginDynamics启动动态,还调用了AccuSnap::GetInstance().EnableSnap(true)启动精确捕捉;
  • _OnRestartTool是一个必须要重写的纯虚函数。当外部事件(如Undo)打断了当前工具时会调用该函数;
  • 调用了_BeginDynamics后,当光标在视图区移动时,_OnDynamicFrame函数会被反复调用。参数ev返回了当前光标位置等信息,我们利用该位置作为基点来创建B样条曲面eeh,然后利用RedrawElems类在视图区以临时元素方式重绘该元素句柄;
  • 当用户在视图区点鼠标的数据键(默认为左键)后会调用到_OnDataButton函数,该函数才真正将生成的B样条曲面eeh添加到模型中。我们还调用了_OnReinitialize函数来重新初始化工具对象;
  • 当用户在视图区点鼠标的拒绝键(默认为右键)后会调用到_OnResetButton函数,在该函数中停止动态并退出本工具。

 2、修改createABsplineSurface函数为如下内容:

void createABsplineSurface (WCharCP unparsed)
{
   PlaceBsSurfaceTool *pTool = new PlaceBsSurfaceTool (0, 0);
   pTool->InstallTool();
}

 3、打开HelloWorld.mke文件,在宏LINKER_LIBRARIES中增加两个库文件BentleyAllocator.lib和DgnView.lib。最终的效果如下:

LINKER_LIBRARIES = $(mdlLibs)bentley.lib \
	               $(mdlLibs)mdlbltin.lib \
        	       $(mdlLibs)BentleyGeom.lib \
                   $(mdlLibs)DgnPlatform.lib \
		           $(mdlLibs)BentleyAllocator.lib \
		           $(mdlLibs)DgnView.lib

4、保存VS中所有文件,切换到黑窗口键入bmake进行编译;

5、启动MicroStation并进入一个三维模型,装载HelloWorld应用后键入HelloWorld Create BsplineSurface并回车启动命令,此时能看到屏幕上一个三维的B样条曲面跟随着光标在移动,当您在视图中点鼠标左键接受时,就会在您指定的点放置一个B样条曲面;

至此,我们已经实现了交互的主要功能。可能您会注意到,在工具工作过程中状态栏没有任何提示信息,工具设置对话框的标题栏显示的只是Tool Settings而不是一个更直观的命令,这说明我们的工具还不够完美。下面让我们继续改造HelloWorld使其完全像MicroStation的一个工具那样工作。

6、回到Visual Studio中,在Header Files下新建一个HelloWorld.h头文件,然后在其中输入如下内容并保存。

#define STRINGLISTID_Commands         1
#define STRINGLISTID_Prompts          2

#define CMDNAME_PlaceBsSurfaceTool    1

#define PROMPT_PlaceBsSurfaceTool     1

 7、在HelloWorld.r中输入以下内容并保存:

MessageList STRINGLISTID_Commands =
{
    {
    { CMDNAME_PlaceBsSurfaceTool,            "放置B样条曲面" }, 
     }
};

MessageList STRINGLISTID_Prompts =
{
    {
    { PROMPT_PlaceBsSurfaceTool,    "指定一个点" }, 
    }
};

 此处定义的内容就是1中第三条提及的工具名标识和工具名字符串,以及工具提示标识和工具提示字符串。

【注】:为了简化项目,我们直接将这些内容放入了HelloWorld.r中。当您读SDK附带的例子时,您会注意到这些内容是单独定义在一个xxxmsg.r资源文件中的,该资源文件是与语言相关的且位于一个叫做english的文件夹下。这样便于项目的国际化。比如,当您想生成项目的法语版本时,您只需要将english文件夹复制成一个平级的french文件夹,然后将french文件下的所有文件中的英文字符串改成法文字符串,最后在mke文件中将langSpec    = $(baseDir)english/定义改成langSpec = $(baseDir)french/并重新编译项目即可。

8、在HelloWorldCmd.r文件的头部包含HelloWorld.h:

#include "HelloWorld.h"

 b. 修改createABsplineSurface函数内容如下:

void createABsplineSurface (char *unparsed)
{
   PlaceBsSurfaceTool *pTool = new PlaceBsSurfaceTool (
                                    CMDNAME_PlaceBsSurfaceTool,
                                    PROMPT_PlaceBsSurfaceTool);
   pTool->InstallTool();
}

 c. 在MdlMain函数中最后增加如下一行代码:

mdlState_registerStringIds (STRINGLISTID_Commands, STRINGLISTID_Prompts);

该函数在系统中注册了两个信息列表STRINGLISTID_Commands和STRINGLISTID_Prompts,一个供工具名(或称命令名)使用,一个供工具提示使用。这样,在new PlaceBsSurfaceTool时的参数CMDNAME_PlaceBsSurfaceTool和PROMPT_PlaceBsSurfaceTool将分别取自这两个消息列表。同样,在HelloWorldCmd.r中的CMDNAME_PlaceBsSurfaceTool也是取自STRINGLISTID_Commands。

10、在MicroStation中卸载HelloWorld,在Visual Studio中保存所有修改的文件,重新编译HelloWorld并进入MicroStation测试,您将看到状态栏有工具名和工具提示,工具设置对话框标题也改换成了我们的工具名而不再是原来的键入命令了。

通过以上工具的实现,我们可以总结出一个简单的DgnPrimitiveTool工具工作示意图如下:

 

除了最常用的DgnPrimitiveTool外还有更复杂的DgnElementSetTool、DgnRegionElementTool、ElementGraphicsTool和LocateSubEntityTool。它们都是支持元素修改的工具基类:DgnElementSetTool用来实现一个基本的修改命令;DgnRegionElementTool用来实现一个创建或修改封闭区域的命令;ElementGraphicsTool用来实现操作智能实体(SmartSolid)的命令,该类对智能实体建立一个高速缓存从而提供操作效率;LocateSubEntityTool也是操作智能实体的工具基类,它允许我们操作到实体的子实体(sub entity)部分,如实体的面、边和顶点。这些类的使用样例在SDK的examples文件夹下都能找到,请参考具体示例深入学习。



猜你喜欢

转载自www.cnblogs.com/JolinZH/p/12821151.html