Questions similar to this appear on the Bentley Discussion Groups. These questions, or something like them asked by MDL and VBA developers, appeared in the MDL discussion group.

Q Questions about element location with MDL pop up occasionally …

A MicroStation uses a state machine when a user creates or locates an element. The state machine provides a way to apply different logic at the various stages of element creation or location. In the case of location logic, which is what we're interested in here, the available states are …

  1. Start (your CMD_XXX function)
  2. Filter (does this element pass my locate criteria?)
  3. Accept (this element has passed my locate criteria)

The best way to start a locate command is by calling mdlState_startModifyCommand() in your command function. Yes, I know that it doesn't sound right, because you want to locate, not modify, an element; but don't let a function name stop you from using it: "What's in a name? That which we call a rose by any other word would smell as sweet".

MDL's state commands initialise MicroStation's location logic. If you want to specify the location criteria, you should call mdlLocate_xxx functions after any mdlState_xxx function, including mdlState_startModifyCommand(). It looks neater if you place location initialisation in its own function: that way, you can share the same locate criteria between several commands.

For hints on using MDL to extract metrics from elements, see this article.

Using Message Lists with MDL Commands

MDL's state functions let you specify the ID of a string resource for command and prompt messages. This enables you to provide your own prompt (e.g. Identify Object instead of MicroStation's Locate Element). However, for this to work you must perform four distinct tasks …

  1. Define message list IDs and individual message IDs in your header file
  2. register your message list IDs with MicroStation using mdlState_registerStringIds()
  3. Insert your command message ID in a command table CMDSTR() macro
  4. Specify your command and prompt message IDs in the mdlState_startXxx function

Message List IDs

First, define your message list IDs in your header (xxx.h) file. The header file is #included by your source (xxx.mc) and command (xxxcmd.r) files.

With MicroStation V8, you can use an enumeration for resource IDs, because the enum keyword is recognised by both mcomp.exe, which compiles your source (.mc) code, and rcomp.exe, which compiles your resource (.r) data …

enum MessageListIDs
{
  MESSAGELISTID_Commands        = 10,
  MESSAGELISTID_Prompts,
  MESSAGELISTID_Messages,
  MESSAGELISTID_Errors,
};

enum MESSAGELISTID_commands
{
  CMDID_LocateSomething         = 1,
};

enum MESSAGELISTID_prompts
{
  PROMPTID_LocateSomething      = 1,
};

enum MESSAGELISTID_messages
{
  MSGID_Measure                 = 1,
};

enum MESSAGELISTID_errors
{
  ERRID_CommandTable            = 1,
  ERRID_Measure,
};

The header file is #included by your source (xxx.mc) and command (xxxcmd.r) files.

Register Command Numbers

MicroStation lets your application respond to an arbitrary number of command keyins. You determine the command syntax, which is specified formally in a command table resource. A command table is an MDL resource (.r) file that you compile and merge with your application.

One result of the resource compilation is a header file containing a list of macro definitions, one for each of your commands. Each macro associates a token (e.g. CMD_MYAPP_DO_SOMETHING) with a unique numeric ID.

You associate the token with a command function (in your .mc file) through a command table array. The array lists each function in your source code with a token. For example …

/////////////////////////////////////////////////////////////////////////////
//	Application command table
MdlCommandNumber  	commandNumbers [] =
{
{	cmd_locateText,			CMD_LOCATE_TEXT		},
{	cmd_locateExit,			CMD_LOCATE_EXIT		},

// This table must be NULL-terminated
0
};

When a user wants to use your application, she uses a set of command key-words that you have published in your documentation. When you register the command number array, MicroStation knows how to associate the keyin with the function that you want it to execute in response. Register the command array with MicroStation like this, usually in your main() entry point …

	status = mdlSystem_registerCommandNumbers (commandNumbers);

There is an example of an MdlCommandNumber table in the LocateExample you can find here.

Register Message IDs

Second, add one line of code to main() to register your message list IDs. You do this with a call to mdlState_registerStringIds() …

//	main()
int main (int argc, char* argv [])
{
  RscFileHandle    rscFileH;	// a resource file handle
  //	Open the resource file that we came out of
  mdlResource_openFile (&rscFileH, NULL, 0);
  //	Load the command table
  if (NULL == mdlParse_loadCommandTable (NULL))
  {
    printf ("%s: unable to load command table\n", mdlSystem_getCurrTaskID ());
    exit (EXIT_FAILURE);
  }
  //	Tell MicroStation to use our messages in commands
  mdlState_registerStringIds (MESSAGELISTID_Commands, MESSAGELISTID_Prompts);
  return SUCCESS;
}

CMDSTR() Command Macro

Third, include your command message ID in a CMDSTR() macro in your commad table resource (.r) file …

/* No. Subtable   CommandClass   Options                         CommandWord */
{  1,  CT_NONE,   LOCATE,        CMDSTR (CMDID_LocateSomething), "CELL"  },

Locate Command Function

Fourth, include your command and prompt message IDs in your command function's call to an mdlState_startXxx function …

mdlState_startModifyCommand (...
                           CMDID_LocateSomething,             // Command ID in your message resource
                           PROMPTID_LocateSomething,          // Prompt ID in your message resource
                           ...
                           );

Example Locate Command

Here's an example locate command. It specifies a datapoint callback function example_locateDatapoint and message list IDs. The mdlState_startXxxx function initialises MicroStation's locate logic, so after that call we specify our own locate criteria function example_setLocateCriteria. It's a handy container for the locate logic that can be shared by several state functions as required …

//	Example locate command
Public void cmdExample_locateSomething (char* unused)
cmdNumber   CMD_EXAMPLE_LOCATE_SOMETHING  // From your command table in mycommand.r 
{
  mdlState_startModifyCommand (cmdExample_locateSomething,    // Reset start this command again
                               example_locateDatapoint,       // Datapoint calls this function
                               NULL,                          // Dynamic function not used here
                               NULL,                          // Show function not used here
                               NULL,                          // Clean function not used here
                               CMDID_LocateSomething,	      // Command ID in your message resource
                               PROMPTID_LocateSomething,      // Prompt ID in your message resource
                               FALSE,                         // Don't use Selection Set
                               0);                            // No additional datapoints required
  example_setLocateCriteria (TRUE);
}
//	Search locate criteria may be common to several commands
void example_setLocateCriteria (BoolInt allowRefs /* Permit user to locate elements in referenced models */)
{
  static int searchMask [] = {  CELL_HEADER_ELM,
                                LINE_ELM,
                                LINE_STRING_ELM,
                                CURVE_ELM,
                                CMPLX_STRING_ELM,
                                ARC_ELM,
                             };
  mdlLocate_init ();
  if (allowRefs)
    mdlLocate_noElemAllowLocked ();
  else
    mdlLocate_noElemNoLocked ();

  mdlLocate_setElemSearchMask (sizeof (searchMask) / sizeof (searchMask [0]), searchMask);
}

Datapoint Callback Function

The datapoint callback function is called once a user has identified an element and that element has passed your locate criteria. This function is a convenient place to obtain element information. For instance, you may want to measure a linear element or take the area of a shape.

The datapoint function has a standard signature …

void example_locateDatapoint (DPoint3d* pt, int view)

In the datapoint callback function, you often want to operate on the element that was located, in which case it's usual to find the element's model reference and file position calling mdlElement_getFilePos() like this …

DgnModelRefP    modelRef = NULL;
ULong           filePos = mdlElement_getFilePos (FILEPOS_CURRENT, &modelRef);

If you're planning to let the user locate a complex or cell element, then use FILEPOS_COMPONENT in place of FILEPOS_CURRENT.

Once you have the located element's file position and model reference, you can read the element data from file into an MSElementUnion or MSElementDescr* …

//	Example datapoint callback function 
void example_locateDatapoint (char* unused)
{
  DgnModelRefP      modelRef    = NULL;
  ULong             filePos     = mdlElement_getFilePos (FILEPOS_COMPONENT, &modelRef);
  MSElementDescr*   pComponent  = NULL;

  if (0 < mdlElmdscr_readToMaster (&pComponent, filePos, modelRef, FALSE, NULL))
  {
    //	Do something with pComponent
  }
  //	Always free memory
  if (NULL != pComponent)
    mdlElmdscr_freeAll (&pComponent);
  //	Usually, you want to start this command again
  mdlState_restartCurrentCommand ();
}

Locate Component: MDL Project

There are some example MDL projects …

Return to MDL articles index.