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.