MDL之编程

初试 MDL 这篇文章中,认识了 MDL 文件结构、程序运行以及编译原理,下面就来讲讲 MDL 设计思路以及如何编写各部分。

设计思路

MDL 如何开始,并能使初设很容易转换成高层次的设计:

首先:

  1. 定义使用者在熟悉的术语中操作的对象类型
  2. 列出对象的属性
  3. 定义对象的放置和操作
  4. 其他需要的功能

进阶:

  1. 确定用于表示应用程序对象的 MicroStation 元素
  2. 确定必须从用户那里收集哪些设置
  3. 设计元素放置和操作工具
  4. 用于其他功能的设计工具和设置框

以上就是在编程应用程序的设计细节之前,需要首先拥有的功能规范,描述应用程序要执行的操作。

以下就是实际编程中 MDL 初始化逻辑:

  1. 为对话框、命令表和消息列表打开资源文件:

    1
    2
    RscFileHandle rfHandle;
    mdlResource_openFile (&rfHandle, NULL, FALSE);
  2. 注册消息列表 id 以用于操作的提示或错误:

    1
    2
    //MSGLIST_commands、MSGLIST_prompts 分别为命令ID、命令提示ID
    mdlState_registerStringIds (MSGLIST_commands, MSGLIST_prompts);
  3. 加载命令表:

    1
    mdlParse_loadCommandTable (NULL);
  4. 为系统事件(比如:【SYSTEM_UNLOAD_PROGRAM:程序尚未加载】、【SYSTEM_RELOAD_PROGRAM:程序重新加载】)设置用户的 hook 函数:

    1
    2
    // 在 SYSTEM_UNLOAD_PROGRAM 卸载时在 unloadFunction 函数中做一些事情,比如存储这是输入的值以便下次使用,不用再次输入,而 unloadFunction 就是 hook 函数,用于在指定的事件发生时触发自定义的操作
    mdlSystem_setFunction (SYSTEM_UNLOAD_PROGRAM, unloadFunction);
  5. 使用 MicroStation 对话框管理器注册对话框 hook 函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //注册对话框 hook 函数组,并关联hook函数ID和函数地址
    mdlDialog_hookPublish (sizeof(uHooks)/sizeof(DialogHookInfo), uHooks);
    // 将hook函数 myLevel_comboHook 和 对话框ID为 HOOKITEMID_MyLevelCombo的关联起来,以监测这个对话框条目生命周期的事件
    DialogHookInfo uHooks[] =
    {
    {HOOKITEMID_MyLevelCombo, (PFDialogHook)myLevel_comboHook },
    }
    //针对对话框条目比如:初始化、创建生成、清除等等进行监测,并触发相应的操作
    void myLevel_comboHook (DialogItemMessage *dimP)
    {
    RawItemHdr *riP = dimP->dialogItemP->rawItemP;
    dimP->msgUnderstood = TRUE;
    switch (dimP->messageType)
    {
    case DITEM_MESSAGE_CREATE:
    {
    //对话框条目生成时触发的操作
    break;
    }
    case DITEM_MESSAGE_DESTROY:
    {
    //对话框条目清除时触发的操作
    break;
    }
    .......
    default:
    //对话框条目其他情况默认触发的操作
    break;
    }
    }
  6. 打开应用程序的初始对话框,初始化完成后,主函数返回,应用程序等待它的对话框 hook 函数、用户 hook 函数或命令函数被调用:

    1
    2
    //其中 DIALOGID_Pal 为对话框资源文件中定义的对话框ID
    mdlDialog_open (NULL, DIALOGID_Pal);
  7. 构建单元库,用于需要一组预定义图符的应用程序表示系统内置的单元库中的对象,而这个单元库由开发人员生成并打包成应用程序

  8. 创建命令表,注册命令表中的 command,并将命令ID与命令触发的方法对应:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Private MdlCommandNumber commandNumbers [] =
    {
    {createALine, CMD_HELLOWORLD_CREATE_LINE},
    {createAComplexShape, CMD_HELLOWORLD_CREATE_COMPLEXSHAPE},
    {createAProjectedSolid, CMD_HELLOWORLD_CREATE_PROJECTEDSOLID},
    {createABsplineSurface, CMD_HELLOWORLD_CREATE_BSPLINESURFACE},
    {connectHitPoints, CMD_HELLOWORLD_CONNECT_HITPOINTS},
    {turnOnViewTransient, CMD_HELLOWORLD_TRANSIENT_ON},
    {turnOffViewTransient, CMD_HELLOWORLD_TRANSIENT_OFF},
    0
    };
    mdlSystem_registerCommandNumbers (commandNumbers);
  9. 编写原始命令逻辑,编写应用程序的所有原始命令例程来处理元素放置和操纵。每一个原始的命令都需要设置状态函数来处理事件,比如数据点、重置和显示动态——当它被拖过屏幕时显示一个元素,有时被称为“弹性连接”。

  10. 为直接命令和实用命令编写逻辑,其中直接命令用于修改内部设置的值,比如:精确绘图快速旋转 RQ,但对话框是一种更优雅的方式来设置内部变量的值,因此编写直接命令的需求减少了。而实用命令不需要来自用户的交互输入,并且不只是设置应用程序变量,还有很多状态控制函数,比如会经常使用的:

    1
    2
    //根据事件规定使用的函数,下面的意思是动态绘制状态时调用函数 line_drawLine 进行动态绘制直线
    mdlState_setFunction (STATE_COMPLEX_DYNAMICS, line_drawLine);
  11. 构建工具栏,工具栏是开始命令的图形图标。当用户单击一个工具时,就会启动适当的命令。如果向用户提供命令,MicroStation 类型的应用程序至少有一个工具箱。在开发的初始阶段,创建工具箱并不是绝对必要的,因为可以在命令行键入命令。然而,当您可以单击一个图标以启动一个命令而不是在命令行键入它时,测试就容易多了。

  12. 构建对话框,对话框显示并收集数据

  13. 创建在线帮助的文档

编程实践

要实现【 MDL 初始化逻辑】里的步骤,主要需要编写以下部分:

  • 消息表(xxxmsg.r)
  • 命令表(xxxcmd.r)
  • 对话框(xxx.r)
  • 源码(xxx.cpp)
  • 编译规则(xxx.mke)

接下来如何编写以上文件,根据 初试 MDL 这篇文章中的示例程序 adrwdemo 的绘制直线的功能来讲解:

消息表

消息列表包含应用程序使用的消息。列表中的每条消息都有相应的消息编号。这些消息号必须是唯一的,并以升序的顺序出现在列表中。MessageList的资源标识符(称为resources ID)与消息编号相结合,惟一地标识列表中的给定消息。

消息表结构的语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
Messagelist <messageid> =
{
{
{<messagenumber>, "<message>"},
......
}
}
结构字段解析:
messageid:识别这个表的唯一编号
messagenumber:唯一的编号且必须以升序排列
message:包含信息的字符串
ps:messageid和messagenumber结合起来,可以唯一地识别列表中的信息

根据语法在 adrwdemomsg.r 编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//资源相关类是在系统头文件rscdefs.h中定义,比如:MessageList
#include "rscdefs.h"
#include "adrwdemo.h"
MessageList MSGLIST_commands =
{
{
{MSG_cmdLine, "Line"},
}
};
MessageList MSGLIST_prompts =
{
{
{MSG_promptEnterPoint, "Enter point"},
}
};

在 MS 操作时界面左下角的提示框就会显示【Line ->Enter point】,提示需要在视图中输入一个数据点。

命令表

命令表定义了应用程序命令的语法。命令表是分层的,其中一个主表向下分支到子命令树中。一个应用程序的命令表是在一个资源源文件中定义的,它被编译成产生两个输出文件,一个是用来解析和验证键的,另一个是唯一标识每个命令的无符号整数的列表。

命令表结构的语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Table <tabteid>=
{
<number>, <subtableid>, <commandclass>, <options>, <commandword>
}
结构字段解析:
tableid:是一个32位无符号整型数。主或根命令表必须赋给一个值为1的
tableid。
number: 定义命令号。
subtableid:指定命令中下一个词的子表。如果没有子表, 则使用 #define CT_NONE。
commandclass:指定命令类别,命令类别是预先定义的,如 INHERIT、PLACEMENT......(INHERIT采用最后一个命令类别,PLACEMENT对于基本命令是最合适的命令类别,DELETE ELEMENT命令属于类操纵,但是DELETE CELL命令位于CELLLIB类中)
options:可用的选项是NONE,DEF,REQ,TRY和CMDSTR(n)。这些选项可以用OR(│ )相连。例如DEF │ TRY(n)。
DEF (缺省), 给特定表指定缺省值。
REQ (要求),告诉命令语法分析程序必须从子表中选择。
TRY (试图分析),这个选项将试图分析子表中的一个值。如果这个值不匹配,则把不匹配部分传递给应用程序。例如:ACTIVESTYLE 3, 这里的3被传递给应用程序。
CMDSTR(n) 每次执行命令时将显示信息表(Messagelist)中的字符串n。
commandword:命令输入字符串

根据语法在 adrwdemocmd.r 编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//避免生成的命令与MicroStation内置命令重复
#pragma suppressREQCmds
#include <rscdefs.h>
//命令表结项选项相关在系统头文件cmdclass.h中定义
#include <cmdclass.h>
#include "adrwdemo.h"
//宏定义命令表 ID
#define CT_NONE 0
#define CT_MAIN 1
#define CT_DEMO 2
Table CT_MAIN =
{
{ 1, CT_DEMO, PLACEMENT, REQ, "DEMO"}
};
Table CT_DEMO =
{
{ 1, CT_NONE, INHERIT, NONE|CMDSTR(MSG_cmdLine), "LINE"}
......
};

在 MS 中顶部菜单栏【实用工具 】-【命令行】键入画线命令【DEMO LINE】,就能启动画线的功能

对话框

工具栏和对话框的GUI相关信息都是存储在资源文件中,通过编写资源文件来控制列表条目、条目资源显示的位置和方式、将被发送到条目hook函数的消息列表以及相关对话框管理器功能的列表。标准对话框条目有:ColorPicker, Generic, GroupBox, IconCmdFrame, Label, LevelMap, ListBox, MenuBar, MLText, OptionButton, PushButton, ScrollBar, Text, ToggleButton, Sash, Scale, RadioButton, MenuBarX, LevelPicker, ToolBox, IconCmdX, ButtonGroup、IconCmdFrameX,这里只列举示例程序中用到的条目类型:

对话框

dl2_dialogboxrs

上图为对话框,其数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
typedef struct dialogboxrsc
{
ULong attributes; /* dialog attributes */
int width; /* dialog coords */
int height; /* dialog coords */
ULong helpInfo; /* help for entire dialog */
char *helpSource; /* help task ID */
long dialogHookId; /* dialog hook ID */
long parentDialogId; /* to inform when destroyed */
#if defined (resource)
char label[]; /* dialog label (title) */
DialogItemRsc itemList[]; /* array of dialog items */
#else
long labelLength; /* length of label (title) */
char label[1]; /* dialog label (title) */
#endif
} DialogBoxRsc;
结构字段解析:
attributes:对话框属性,枚举如下:
DIALOGATTR_DEFAULT:默认的对话框类型,未设置属性
DIALOGATTR_GROWABLE:对话框可重定尺寸,不受width、height影响
DIALOGATTR_NORIGHTICONS:对话框右上角没有最小化、最大化按钮只剩关闭按钮DIALOGATTR_MODAL: 模态框,对话框右上角一个操作按钮都没有
DIALOGATTR_UNCLOSEABLE:不能关闭对话框
DIAIOGATTR_SINKABlE:对话框可送到视图窗口后面
DIALOGATTR_NOAUTOSWITCH:键盘焦聚不能自动打开另一个窗口的开关
DIALOGATTR_CLOSEONNEW:当打开一个新设计文件时关闭对话框
DIALOGATTR_ALWAYSSETSTATE:当用户与条目交互时设置条目的状态
......
width、height:对话框的宽、高由定义在<dlogbox.h>中的三个常量指定:
#define DCOORD_RESOLUTION 12
#define XC (DCOORD_RESOLUTION/ 2)
#define YC DCOORD_RESOLUTON
常量DCOORD_RESOLUTION被规定为12个象素。XC是一个字符的宽度, 是字符高的一半。YC是一个
字符的高。
helpinfo、helpSource:为这个应用定义帮助功能。如果不存在帮助,就分别设置
为NOHELP和MHELPo
dialogHookId:hook函数ID,监测这个对话框条目生命周期的事件
parentDialogId:当一个对话框需要启动另一个对话框时,指定父对话框的ID。然后,这个字段用于确定当子对话框被销毁时要通知哪个对话框。如果对话框没有从另一个对话框中启动,或者父对话框不需要被告知子对话框的销毁情况,请使用NOPARENTID。
label:对话框标题
itemList:对话框列通用条目DialogItemRsc的数组

对话框通用条目

mdl2_comitem

红色方框内为对话框通用条目,其数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
typedef struct dialogitemrsc
{
Sextent extent; /* item area, origin (in dialog coords), if
width/height is zero, use dimensions
specified in item */
long type; /* item type */
long id; /* item ID */
byte attributes; /* item attributes */
long itemArg; /* item argument */
#if defined (resource)
char label[]; /* item label */
char auxInfo[]; /* item auxiliary information */
#else
int labelLength; /* length of item label */
char label[1]; /* item label */
#endif
} DialogItemRsc;
结构字段解析:
extent:对话框条目的坐标和宽高,Sextent定义如下:
typedef struct spoint2d
{
short x;
short y;
}Spoint2d, SPoint2d;
typedef struct extent
{
SPoint2d origin; / * upper left */
short width;
short height;
}Sextent;
type:指定要创建的条目的类型,枚举类型有:ColorPicker, Generic, GroupBox, IconCmdFrame, Label, LevelMap, ListBox, MenuBar, MLText, OptionButton, PushButton, ScrollBar, Text, ToggleButton, Sash, Scale, RadioButton, MenuBarX, LevelPicker, ToolBox, IconCmdX, ButtonGroup、IconCmdFrameX.
id:指定要加载的条目实例的资源ID
attributes:指定项目的初始状态,启动【ON】允许对物品进行操作或禁用。一个禁用的项目被调用,不能接受输入焦点,并且忽略用户操作。大多数项目都应该启用【ON】。
itemArg:为不同的项目类型包含不同的值。比如:label 条目用这个字段指定使用的理由,大多数条目当前没有使用这个字段
label:表示一个字符串,它允许盖特定对话框中条目的标签。默认标签在每个条目资源中定义。在对话框中指定的标签覆盖条目资源中指定的任何标签。该字段允许使用条目的已定义行为,但以不同的方式标记条目,标签的使用取决于项目类型。
auxInfo:包含额外的item-specific信息。实际该字段仅用于覆盖条目资源中定义的接入【变量】字符串,使条目能够按照项目资源中定义的方式进行操作,但是会影响到不同的应用程序变量。

图标命令框

mdl2_IconCmdFrameRsc

红色方框内为图标命令框,其数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct ditem_iconcmdframersc
{
int nColumns;
int nRows;
ULong helpInfo;
ULong helpSource;
#if defined (resource)
char label[];
DialogItemSpec iconPieces[];
#else
long labelLength;
char label[1];
#endif
} DItem_IconCmdFrameRsc;
结构字段解析:
nColumns、nRows:图标命令框中图标的列数和行数
label:指定图标命令框架的标题。对话框标题将被设置为这个字符串的值
iconPieces:指定图标命令框架的内容。iconPieces是一组对话框的成员。图标成员的类型字段可以是IconCmd或IconCmdPalette。iconPieces成员的id字段表明ditemiconcmdrsc或ditemiconcmdpalettersc的实例加载

图标命令框条目

mdl2_item

红色方框内为图标命令框条目,其数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef struct ditem_iconcmdrsc
{
ULong helpInfo;
#if defined (resource)
char helpTaskId[];
ULong attributes;
ULong commandNumber;
char commandTaskId[];
char unparsed[]
char enabledAccessStr[];
DialogItemRsc iconItems[];
#else
long helpTaskLen;
char helpTaskId[1];
#endif
} DItem_IconCmdRsc;
结构字段解析:
attributes:指定图标命令的属性。这个字段通常为0,是通过将属性表中的常量与逻辑运算符|组合在一起构造的,有两个枚举值:
ICONCMDATTR_DONTSINGLESHOT:表示这个图标命令不能双击进入单发操作模式。默认情况下,双击图标会导致微站点为选定的图标输入单发模式。
ICONCMDATTR_DONTAUTOSELECT:表示当与图标相关联的命令被排队到 MicroStation 时,这个图标资源不应该被高亮显示(选中)。
commandTaskId:指定将要执行与命令编号相关联的命令的任务。
将comandTaskId设置为OTASKID,它被定义为空字符串,表明拥有(最初创建)对话框的任务应该执行该命令。
将comandTaskId设置为MTASKID,它被定义为“+”,表明应该使用微站来执行该命令。(命令号必须是在cmdlist.h中定义的一个微站命令号。)
如果您需要指定一个与所有者或 MicroStation 不同的任务,请将任务名称放在这里。
enabledAccessStr:如果未使用,设置为""。
iconItems:一个DialogItemRsc数组和与图标命令关联的弹出对话框项列表。创建此列表的方式与创建普通对话框项列表的方式相同,请参照“DialogBoxRsc Structure”和“DialogBoxRsc Structure”。只有包含在图标命令面板中的图标命令才能有项目列表。图标命令帧不会显示弹出式项目列表。当在图标命令项列表规范中使用时,DialogItemRsc的区段成员指定相对于父图标命令面板左下角的位置,而不是包含图标命令的对话框左上角。

开关状态按钮

mdl2_tbtn

红色方框内开关状态按钮,其数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct ditem_togglebuttonrsc
{
ULong commandNumber;
ULong commandSource;
long synonymsId;
ULong helpInfo;
ULong helpSource;
long itemHookId;
long itemHookArg;
ULong mask;
char invertFlag;
#if defined (resource)
char label[];
char accessStr[];
#else
long labelLength;
char label[1];
#endif
} DItem_ToggleButtonRsc;
结构字段解析:
SynonymsId:定义在对话条目被修改时与所做的修改相对应的对话条目。最好的例子是
ColorPicker对话条目, 它有SynonysmsId, 如Text Item。在ColorPicker改变时, Text Item修改为ColorPicker选择的相应的颜色编号。
itemHookId:定义hook函数的ID。如果不用hook函数, 则在这个字段中指定为NOH00K。hook函数
允许我们修改对话框和条目的缺省动作。
itemHookArg:为hook函数规定变量。如不需要变量, 则在这个字段中使用NOARG。
accessStr:对话框条目控制的变量。例如, 我们想使对话框中的任何变化都反映在变量
adrwdemoInfo.showAxes中, 所以, accessStr将是"adrwdemoInfo.showAxes"。对话框能识别这个变量,因为它已被说明。

命令列表条目

CmdItemListRsc 基于命令,(命令编号被用作ID)CmdItemListRsc 让 MicroStation 无论如何调用该命令,都可以找到当前命令的工具设置,以此解决由于工具设置项列表与图标相关联,(而不是图标执行的命令),只有当图标在内存中(显示)时才能找到工具设置。在检查 IconCmdRsc 资源中的条目列表之前,MicroStation 现在寻就会找与当前命令相关联的 CmdItemListRsc。

1
2
3
4
CmdItemListRsc <commandID> =
{{
DialogItemRsc itemList[];
}};

对话框管理访问字符串

假设开发按钮的字段 label 为变量 adrwdemoInfo.showAxe,如下:

1
2
3
4
5
6
7
8
/*
开关状态按钮
*/
DItem_ToggleButtonRsc TOGGLEID_ShowAxes =
{
NOCMD, MCMD, NOSYNONYM, NOHELP, MCMD, NOHOOK, NOARG,
NOMASK, NOINVERT, TOGGLE_ShowAxes, "adrwdemoInfo.showAxes"
};

因为访问字符串为检查和修改对话框条目定义变量、结构和结构的指针,所以必须通过发布变量、结构或指针, 使对话框条目能够管理我们的数据。实现分为两步:

  • xxxtyp.mt中定义结构和联合的说明,这个文件含有包含文件和publishStructures语句,publishStructures 语句识别在资源源文件中定义的结构。这个结构不能含有动态说明(分配空间的那些说明)或可执行语句:

    1
    2
    #include "adrwdemo.h"
    publishStructures (adrwdemoinfo);

    以此来发布定义在 adrwdemo.h 中的用于设置是否启用结构平面或者显示坐标轴的全局数据结构体:

    1
    2
    3
    4
    5
    typedef struct adrwdemoinfo
    {
    int showAxes;
    int useCPlane;
    } AdrwdemoInfo;
  • 定义符号集,符号集是一个指向内存区域的指针。在这个内存区域内对话条目可以找到应用的变量、结构和指针,为了建立并公布要存取的结构 AdrwdemoInfo,在 .cpp 程序入口函数中编写如下:

    1
    2
    3
    4
    5
    //使setP符号集初始化,可见标志被分配给每一个符号。在搜寻过程中检查可见标志来决定符号集是否包括在搜寻中。当这些符号用于对话框时, 我们规定VISIBILITY_DIALOG_BOX。这些符号如果用于计算器或预处理器,我们则指定VISIBILITY_CALCVLATOR。如果符号同时用于二者,则规定(VISIBILITY_DIALOG_BOX\ VISIBILITY_CALULATOR)。若符号与调试程序一起使用, 则我们规定VISIBILITY_DBUGGER。
    SymbolSet *setP = mdlCExpression_initializeSet (VISIBILITY_DIALOG_BOX, 0, 0);
    //发布要使用的符号
    mdlDialog_publishComplexVariable (setP, "adrwdemoinfo",
    "adrwdemoInfo", &adrwdemoInfo);

根据各个对话框条目的语法,在 adrwdemo.r 编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <rscdefs.h>
#include <cmdlist.h>
//对话框相关类在系统头文件dlogbox.h中定义
#include <dlogbox.h>
#include "adrwdemo.h"
#include "adrwdcmd.h"
#include "adrwdtxt.h"
/*
MDL应用程序(MA)和DLL之间的链接,仍然有一个MA文件被创建和加载,其中包含对话框资源以及DllMdlApp类型的资源,它告诉MA加载DLL。因此需要在一个.r文件中创建这个文件,该文件将被放入应用程序文件中。通常,这是在命令表资源文件的底部完成的,但是它可以在应用程序中的任何资源文件中。
*/
#define DLLAPP_PRIMARY 1
DllMdlApp DLLAPP_PRIMARY =
{
"ADRWDEMO", "adrwdemo" // taskid, dllName
}
/*
构建对话框
TITLE_Palette 是定义在 adrwdtxt.h 中的字符串"AccuDraw Demo"
*/
DialogBoxRsc DIALOGID_Pal =
{
DIALOGATTR_DEFAULT | DIALOGATTR_NORIGHTICONS,
XC, YC, NOHELP, MHELP, NOHOOK, NOPARENTID, TITLE_Palette,
{
{
{0,0,0,0}, IconCmdFrame, ICONCMDFRAMEID_Frame, ON, 0, "", ""},
}
};
/*
图标命令框
*/
DItem_IconCmdFrameRsc ICONCMDFRAMEID_Frame =
{
5,1,NOHELP,MHELP,TITLE_Palette,
{
{IconCmd, ICONCMDID_Line},
{IconCmd, ICONCMDID_Circle},
{IconCmd, ICONCMDID_Rectangle},
{IconCmd, ICONCMDID_Rectangle2},
{IconCmd, ICONCMDID_ChangeCircle},
}
};
/*
图标命令框条目
其中 MSVERSION 随着每个版本的变化而变化,因此MDL程序员可以有条件地为特定的版本编译代码.
extendedAttributes:扩展属性类型文本,用于鼠标移到图标命令条目上时显示命令描述提示
*/
DItem_IconCmdRsc ICONCMDID_Line =
{
NOHELP, MHELP, ICONCMDATTR_DONTSINGLESHOT,
CMD_DEMO_LINE, OTASKID, "", "",{}
#if defined (MSVERSION) && (MSVERSION >= 0x550)
}
extendedAttributes
{
{
{EXTATTR_FLYTEXT, TXT_Flyover_DemoLine},
{EXTATTR_BALLOON, TXT_Balloon_DemoLine},
}
#endif
};
#if defined (MSVERSION) && (MSVERSION >= 0x551)
CmdItemListRsc CMD_DEMO_LINE =
{{
{
{0,GENY(1),0,0}, ToggleButton, TOGGLEID_ShowAxes, ON, 0, "", ""
},
}};
#endif
/*
开关状态按钮
*/
DItem_ToggleButtonRsc TOGGLEID_ShowAxes =
{
NOCMD, MCMD, NOSYNONYM, NOHELP, MCMD, NOHOOK, NOARG,
NOMASK, NOINVERT, TOGGLE_ShowAxes, "adrwdemoInfo.showAxes"
};

源码

adrwdemo.cpp 文件的画线功能由以下三部分组成:

  1. 引入头文件,涉及用到的宏定义、全局变量以及函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <MicroStationAPI.h>
    #include <cmdlist.h>
    #include <dlogman.fdf>
    #include <AccuDraw.h>
    #include <mselmdsc.fdf>
    #include "adrwdemo.h"
    #include "adrwdemocmd.h"
    #include "adrwdtxt.h
  2. 程序入口函数【 extern “C” DLLEXPORT int MdlMain (int argc, char *argv[]) { }】,编写参照【MDL初始化逻辑】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    extern "C" DLLEXPORT int MdlMain (int argc, char *argv[])
    {
    RscFileHandle rfHandle;
    SymbolSet *setP;
    //打开资源文件,载入内存
    mdlResource_openFile (&rfHandle, NULL, FALSE);
    //加载命令表
    mdlParse_loadCommandTable (NULL);
    //创建命令表,注册命令表中的 command,并将命令ID与命令触发的方法对应
    Private MdlCommandNumber commandNumbers [] =
    {
    {line, CMD_DEMO_LINE},
    {circle, CMD_DEMO_CIRCLE},
    {rect, CMD_DEMO_RECTANGLE},
    {changeCircle, CMD_DEMO_CHANGE},
    {rect2, CMD_DEMO_RECTANGLE2},
    0
    };
    mdlSystem_registerCommandNumbers (commandNumbers);
    //注册消息列表 id 以用于操作的提示或错误
    mdlState_registerStringIds (MSGLIST_commands, MSGLIST_prompts);
    /*
    使setP符号集初始化,并发布使用的符号,符号集是一个指向内存区域的指针。在这个内存区域内对话条目可以找到应用的变量、结构和指针,为了建立并公布要存取的结构 adrwdemoinfo
    */
    setP = mdlCExpression_initializeSet (VISIBILITY_DIALOG_BOX, 0, 0);
    mdlDialog_publishComplexVariable (setP, "adrwdemoinfo",
    "adrwdemoInfo", &adrwdemoInfo);
    adrwdemoInfo.showAxes = FALSE;
    打开应用程序的初始对话框
    mdlDialog_open (NULL, DIALOGID_Pal);
    return 0;
    }

    当程序运行完这个函数,视图中就会显示 adrwdemo 工具框。

  3. 画线功能函数,MS内部机制是事件驱动,由事件队列统一管理调度,所以当按下 adrwdemo 工具框中的画线图标命令,这个画线任务就会进入事件队列,由事件队列处理器调用画线功能函数,所以需要使用函数mdlState_startPrimitive编写一个原始命令,由原始命令例程来处理元素放置和操纵,参考【MDL初始逻辑】第9条

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    //画线初始命令
    Private void line (char *unparsed)
    {
    /*
    调用mdlState_startPrimitive基本命令生成元素,并调用line_dataPt1开始画线,并配置宏定义字符串MSG_cmdLine 和 MSG_promptEnterPoint 组成"Line->enter point"的命令提示
    */
    mdlState_startPrimitive (line_dataPt1, line, MSG_cmdLine, MSG_promptEnterPoint);
    }
    //画线输入第一个数据点,作为画线的起始点
    Private void line_dataPt1 (Dpoint3d *ptP, int view)
    {
    //存储确定的第一个数据点
    ptStack[0] = *ptP;
    //设置事件触发函数,当输入数据点时触发函数line_dataPt2,动态绘制时触发函数line_drawLine
    mdlState_setFunction (STATE_DATAPOINT, line_dataPt2);
    mdlState_setFunction (STATE_COMPLEX_DYNAMICS, line_drawLine);
    //设置输出的命令提示
    mdlOutput_rscPrintf (MSG_PROMPT, NULL, MSGLIST_prompts,
    MSG_promptEnterPoint);
    }
    //画线输入第二个数据点,作为画线的终结点
    Private void line_dataPt2 (Dpoint3d *ptP, int view)
    {
    double length; /* length of line placed */
    DVec3d xAxisVec; /* unit vector -- direction of line */
    //传入第二个数据点,根据两点绘制直线
    line_drawLine (ptP, view, NORMALDRAW);
    //求出两点法向量的距离
    length = mdlVec_computeNormal (&xAxisVec, ptP, &ptStack[0]);
    //设置精确绘图 AccuDraw 的位置、距离、方向等,以便更好地操作,提高工具的可用性和能力
    mdlState_setAccudrawContext (
    ACCUDRAW_SetDistance |
    ACCUDRAW_SetXAxis,
    NULL, /* origin: last point OK */
    NULL, /* delta (not used here) */
    &length, /* distance */
    NULL, /* angle (not used here) */
    &xAxisVec /* x axis: last line */
    );
    //将第二个数据点设置为第一个
    ptStack[0] = *ptP;
    mdlState_setFunction (STATE_COMPLEX_DYNAMICS, line_drawLine);
    mdlOutput_rscPrintf (MSG_PROMPT, NULL, MSGLIST_prompts,
    MSG_promptEnterPoint);
    }
    //结合line_dataPt1和line_dataPt2两个数据点画线
    Private void line_drawLine (Dpoint3d *ptP, int view, MstnDrawMode drawMode)
    {
    MSElement elem;
    Dpoint3d points[2];
    points[0] = ptStack[0];
    points[1] = *ptP;
    //根据传入的两个点,创建元素
    mdlLine_create (&elem, NULL, points);
    //显示创建的直线元素
    mdlElement_display (&elem, drawMode);
    //当完成绘制,就将元素头部中的属性位设置为未锁定、新元素,而不修改,也就是在确定第二个点之后在视图生成固定位置的直线,而不动态改变了
    if (drawMode == NORMALDRAW)
    mdlElement_add (&elem);
    }

编译规则

MDL 的编译文件是 makefile 类型的文件,拥有自己的语法和变量,在 Linux 中也用来进行宏编译,它的好处:

  • 简化编译时所需要执行的命令
  • 若在编译完成之后,修改了某个源码文件,则 make 仅会针对被修改了的文件进行编译。其他的目标文件不会被更改
  • 最后可以依照相依性来更新执行文件

adrwdemo.mke 编写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//定义mdl程序名
appName = adrwdemo
/*
定义mdl目标文件路径,其中
$(o):mdl内置变量,objects 文件的路径,如:C:\Program Files (x86)\Bentley\MicroStation V8i (SELECTseries)\MicroStation\mdl\objects
$(oext):objects 文件后缀.obj
*/
appObjects = $(o)$(appName)$(oext)
//定义资源文件路径
appRscs = $(o)$(appName).rsc $(o)$(appName)cmd.rsc $(o)$(appName)typ.rsc $(o)$(appName)msg.rsc
//定义当前makefile文件的路径
baseDir = $(_MakeFilePath)
//引入 mdl 内置的编译规则
%include mdl.mki
dirToSearch = $(MSMDE)/mdl/MicroStationAPI
%include cincapnd.mki
//创建objects、rscObjects、reqdObjs三个文件夹,路径在C:\Program Files (x86)\Bentley\MicroStation V8i (SELECTseries)\MicroStation\mdl,用来放置编译后的文件
always:
~mkdir $(o)
~mkdir $(rscObjects)
~mkdir $(reqdObjs)
/*
设置dlmlink.mkil里面的变量,以便调用dlmlink,其中注意 DLM_LIBRARY_FILES 源码依赖的类库都要添加进来,DLM_OBJECT_FILES 需要编译的.cpp文件都要添加进来
*/
DLM_NO_SIGN = 1
DLM_OBJECT_DEST = $(o)
DLM_NAME = $(appName)
DLM_OBJECT_FILES = $(appObjects)
DLM_NO_DLS = 1
DLM_NO_DEF = 1
DLM_NOENTRY = 1
DLM_DEST = $(mdlapps)
DLM_NO_DELAYLOAD = 1
DLM_NO_NTBSADDR = 1
DLM_LIBRARY_FILES = $(mdlLibs)BentleyDgn.lib \
$(mdlLibs)toolsubs.lib \
$(mdlLibs)ditemlib.lib \
$(mdlLibs)mdllib.lib \
$(mdlLibs)msbspline.lib \
$(mdlLibs)mtg.lib
/*
编译资源文件,编译语法:
{
目标(target):目标文件1 目标文件2
<tab>编译规则
}
第一行中的目标就是要建立的信息,而目标文件就是具有相关性的源文件,第二行开头需要<tab>缩进一下再写编译规则,如果为空行则表示默认使用mdl.mki内置的编译规则
*/
$(baseDir)$(appName)cmd.h : $(baseDir)$(appName)cmd.r
$(o)$(appName)cmd.rsc : $(baseDir)$(appName)cmd.r
$(o)$(appName)typ.r : $(baseDir)$(appName)typ.mt
$(o)$(appName)typ.rsc : $(o)$(appName)typ.r
$(o)$(appName)msg.rsc : $(baseDir)$(appName)msg.r
$(o)$(appName).rsc : $(baseDir)$(appName).r
//根据上一步编译好的.rsc文件生成可执行文件.ma
$(mdlapps)$(appName).ma : $(appRscs)
$(msg)
> $(o)make.opt
-o$@
$(appRscs)
<
$(RLibCmd) @$(o)make.opt
~time
//调动VS提供的编译器cl,将源文件.cpp编译成目标文件.obj,由 dmlink.mki 定义的编译规则链接到DLL
$(o)$(appName)$(oext) : $(baseDir)$(appName).cpp
%include dlmlink.mki

编写完成,运行程序,实现下图红框中的画线功能:

mdl2_pargramrun