Introduction

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.

MicroStation Development: User Interface Options

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.

Dialog Item Hook Functions

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.

ListBox and ComboBox Dialog Items

ComboBox and ListBox dialog items require programming support to work correctly. Their common feature is the use of a ListModel.

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.

Use of ListModel

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;
    }
  }
}

Dialog Item Hook Handler Class

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.

ListModel Manager Template Class

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;
}

Classes that Inherit ListModel Manager

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;
}

Questions

Post questions about C++ and the MicroStationAPI to the MicroStation Programming Forum.