The MicroStation Development Library (MDL) and MicroStationAPI provide APIs for developers wanting to create custom applications for MicroStation® from Bentley Systems. We create a MicroStation application as a DLL, written using C++ and built with the Microsoft C++ compiler and linker provided with Visual Studio.
When editing your source code, you can choose whether to use Microsoft Visual Studio, Microsoft Visual Studio Code, or one of your favourite text editors.
When building your app, you can use Visual Studio or the Bentley Systems make (bmake) tools.
When we design an application for MicroStation CONNECT we have a choice of programming languages …
This article concerns C++ and the classes that support MicroStation dialog items.
Most MicroStation dialog items have an intuitive default behaviour:
a PushButton starts a command, a Text item lets a user enter text, and so on.
This article describes a hook class implemented using a C++ template class.
It provides support for the ListModel data structure used to populate
ComboBox and ListBox dialog items.
When we want more than the default behaviour a dialog item can offer, we can write hook functions to provide custom behaviour. Hook functions have let us customise the user interface since the MicroStation Development Language (MDL) was introduced with MicroStation v4 decades ago.
With MicroStation CONNECT the MicroStationAPI lets us use hook classes rather than hook functions to implement custom functionality. Nearly every dialog item has the ability to specify a hook ID. The hook function IDs and hook class IDs are specified to MicroStation in a hooks table. Here's an example …
Public DialogHookInfo uHooks[] =
{
…
{HOOK_CBO_LevelName, (PFDialogHook)CboLevelName::HookResolve },
…
};
We notify MicroStation about our hook functions by publishing that table, usually early in our MicroStation app. …
mdlDialog_hookPublish (sizeof (uHooks) / sizeof (DialogHookInfo), uHooks);
If the above seems to be gobbledegook to you, then take time to look at the code samples delivered with the MicroStation SDK.
ComboBox and ListBox dialog items require programming support to work correctly.
Their common feature is the use of a ListModel.
A ListModel is a dynamically-allocated data structure that represents a grid.
The grid contains ListRows and ListColumns.
At the intersection of a ListRow and ListColumn is a ListCell.
A ListCell displays textual data, but it may contain
— in addition to the display string —
data of other types.
For more information about the ListModel look for the mdlListModel_api in MicroStationAPI help,
and this
article about ListModels.
We use a ListModel first by filling it with data, then assigning it to a ComboBox or ListBox.
Usually that assignment takes place in the _OnCreate() or _OnInit() method of the hook class.
The relevant calls are mdlDialog_comboBoxSetListModelP() and mdlDialog_listBoxSetListModelP().
Look up those functions in MicroStationAPI help and you'll see that they are pretty much identical.
Because a ListModel is dynamically allocated, we must deallocate it correctly to avoid a memory leak.
We use the _OnDestroy() hook class method as an opportunity to call either
mdlDialog_comboBoxGetListModelP() or mdlDialog_listBoxGetListModelP() to get the ListModel pointer.
Then we call mdlListModel_destroy() to free its memory.
There are examples provided by the SDK, including cstexmpl and ViewGroupExample.
Here's an outline of what's going on in the old, procedural, way of writing a dialog item hook handler.
It's an extract from the ViewGroupExample …
static void viewgroup_listHook (DialogItemMessage* dimP)
{
DialogItem* diP = dimP->dialogItemP;
RawItemHdr* rihP = diP->rawItemP;
switch (dimP->messageType)
{
case DITEM_MESSAGE_CREATE:
{
ListModelP listModelP = MyFunctionCreatesListModel ();
// Assign the ListModel to the dialog item
mdlDialog_listBoxSetListModelP (rihP, listModelP, 0);
break;
}
case DITEM_MESSAGE_DESTROY:
{
// Retrieve the ListModel from the dialog item
ListModelP listModelP = mdlDialog_listBoxGetListModelP(rihP);
if (listModelP)
mdlListModel_destroy (listModelP, true);
break;
}
}
}
The DialogItemHookHandler class is to be found in the MicroStationAPI help.
It is declared in header file <dlogitem.h>.
The class provides a number of event handler hooks.
They are mostly virtual methods, meaning that we can override them in our own classes or simply ignore them.
Those that are relevant to this discussion are
DialogItemHookHandler::_OnCreate (DialogItemCreateArgsR create) and DialogItemHookHandler::_OnDestroy ().
Using those methods is straightforward: we declare a class that inherits from DialogItemHookHandler.
In our class, we implement _OnCreate () and _OnDestroy ().
Something like this …
class MyComboBoxHandler : public DialogItemHookHandler
{
bool _OnCreate (DialogItemCreateArgsR create) override
{
ListModelP listModelP = MyFunctionCreatesListModel ();
return SUCCESS == mdlDialog_comboBoxSetListModelP (GetRawItem (), listModelP, 1);
}
void _OnDestroy () override
{
ListModelP listModelP = mdlDialog_comboBoxGetListModelP (GetRawItem ());
if (listModelP)
mdlListModel_destroy (listModelP, true);
}
};
Here's a similar class to handle a ListBox …
class MyListBoxHandler : public DialogItemHookHandler
{
bool _OnCreate (DialogItemCreateArgsR create) override
{
ListModelP listModelP = MyFunctionCreatesListModel ();
return SUCCESS == mdlDialog_listBoxSetListModelP (GetRawItem (), listModelP, 1);
}
void _OnDestroy () override
{
ListModelP listModel = mdlDialog_listBoxGetListModelP (GetRawItem ());
if (listModelP)
mdlListModel_destroy (listModelP, true);
}
};
Notice that the two classes are similar, except for the calls to mdlDialog_comboBoxXxx and mdlDialog_listBoxXxx.
You may have heard the maxim: "Don't write the same code twice!".
It's covered by the programming
rule of three.
Well this isn't exactly identical code, but its similarity begs for a way to reduce the amount of code.
Because we're dealing with C++ a template solution springs to mind.
The ListModelManager class inherits from the DialogItemHookHandler class,
and is designed to be inherited in turn by your hook handler class.
It implements the _OnDestroy() method so that the class ListModel
is always correctly destroyed.
It provides some helper methods, including AssignListModel() and GetListModel()
that work correctly whether the dialog item is a ComboBox or ListModel.
What makes ListModelManager a template class?
Its declaration takes an integer ID.
The ID is the MicroStation resource ID of a
ComboBox (RTYPE_ComboBox) or ListModel (RTYPE_ListModel).
For the definition of those values, see MicroStationAPI header file
RmgrTools/Tools/rtypes.r.h
// Specialise instantiated class by the non-type template parameter RType (resource type) template <int RType> struct ListModelManager : DialogItemHookHandler { ListModelManager (MSDialogP dbP, DialogItemP diP) : DialogItemHookHandler (dbP, diP) { } virtual ~ListModelManager () {} /// Deallocate a ListModel assigned to this dialog item. virtual bool _OnDestroy () override; /// Assign a ListModel to this dialog item. bool AssignListModel (ListModelP listModel); /// Get the ListModel assigned to this dialog item. ListModelP GetListModel (); };
The implementation of the class methods provides the customisation we want for ComboBox or ListBox.
Remind yourself that the purpose of ListModelManager is to handle the item-specific functions that
get/set the ListModel for the appropriate dialog item.
Here is the templatised GetListModel implementation …
#include <HookBaseClasses.h>
#include <Mstn/MdlApi/miscilib.fdf>
#include <Mstn/MdlApi/listmodel.fdf>
USING_NAMESPACE_BENTLEY_DGNPLATFORM;
ListModelP ListModelManager<RTYPE_ComboBox>::GetListModel ()
{
/// GetRawItem() is a member of the base class DialogItemHookHandler
return mdlDialog_comboBoxGetListModelP (GetRawItem ());
}
ListModelP ListModelManager<RTYPE_ListBox>::GetListModel ()
{
return mdlDialog_listBoxGetListModelP (GetRawItem ());
}
Similarly for the specialised AssignListModel implementation …
bool ListModelManager<RTYPE_ComboBox>::AssignListModel (ListModelP listModel)
{
return SUCCESS == mdlDialog_comboBoxSetListModelP (GetRawItem (), listModel);
}
bool ListModelManager<RTYPE_ListBox>::AssignListModel (ListModelP listModel)
{
const int& Unused { 0 };
return SUCCESS == mdlDialog_listBoxSetListModelP (GetRawItem (), listModel, Unused);
}
And the templatised _OnDestroy implementation …
bool ListModelManager<RTYPE_ComboBox>::_OnDestroy ()
{
return DestroyListModel (GetListModel ());
}
bool ListModelManager<RTYPE_ListBox>:_OnDestroy ()
{
return DestroyListModel (GetListModel ());
}
/// DestroyListModel is a utility function that destroys any ListModel bool DestroyListModel (ListModelP listModel) { if (listModel) { mdlListModel_destroy (listModel, true); return true; } //else return false; }
Inheriting classes — your dialog item hook classes — don't have to concern themselves with the detail of assigning and
deallocating ListModel memory.
The ListModelManager base class takes care of that.
Here's the declaration of an inheriting dialog item hook handler class …
#include "HookBaseClasses.h"
struct CboTextStyleHandler : ListModelManager<RTYPE_ComboBox>
{
// Dialog Hook instantiation function and other boilerplate code
…
// Item Hook Handler Constructor
CboTextStyleHandler (MSDialogP dbP, DialogItemP diP) : ListModelManager<RTYPE_ComboBox> (dbP, diP) {}
// Item Hook Handler Method overrides
virtual bool _OnCreate (DialogItemCreateArgsR create) override;
// _OnDestroy() is implemented in the base class ListModelManager<RTYPE_ComboBox>
// Other hook handler method overrides...
};
The implementation of _OnCreate() is used to construct a ListModel that will be assigned
to this ComboBox …
bool CboTextStyleHandler::_OnCreate (DialogItemCreateArgsR create)
{
TextStyleMgr styleMgr;
ListModelP listModel { nullptr };
if (0 < styleMgr.CreateListModel (listModel))
{
// AssignListModel() is implemented in the base class ListModelManager<RTYPE_ComboBox>
return AssignListModel (listModel);
}
return false;
}
Post questions about C++ and the MicroStationAPI to the MicroStation Programming Forum.