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.
Questions similar to this appear on the MicroStation Programming Forum.
Q Questions about ListModels pop up occasionally …
ListModel
? ListModel
Cell Editor? ListModel
? ListModel
in a dialog hook class?
Mark Anderson — now, alas, no longer with Bentley Systems — wrote the original
DataSheet example when MicroStation V8 was young.
His goal was to show how to use a ListBox
to display a ListModel
's contents in a dialog box.
We have taken this example a step further.
We show how to assign a ListCell
editor to a ListModel
.
A cell editor provides an in-place user input device, such as a text box or combo box.
The editor appears automatically when a user clicks on a cell in a ListBox
.
We also show how to apply a colour to a ListBox
cell,
using a MicroStation ColorChooser
dialog item to choose a colour.
Visit the DataSheet article to learn more.
ListBoxes
are the devices used to display multiple rows of text to
your users. Behind a ListBox
is a data model that provides the list to be displayed.
The ListModel
provides a multi-row, multi-column data model.
A ListModel
contains an arbitrary number of ListRow
s.
Each ListRow
contains one or more ListColumn
s.
At the intersection of each ListRow
and ListColumn
is a ListCell
.
A ListCell
separates presentation from content.
That is, its display text may be different to the stored value.
For example, you might want to store a decimal value but display it as a string formatted to the user locale.
The header file for ListModel
s is listmodel.fdf
.
#include <Mstn/MdlApi/listmodel.fdf>
As an C++ programmer, you specify the number of columns when you create a ListModel
.
You populate a ListModel
by creating ListRow
objects and inserting
each one into the ListModel
. Each ListRow
object contains
a number of ListCell
objects, corresponding to the columns of the data model.
You can specify a cell editor for a ListModel
column by obtaining a pointer to a ListCell
and assigning it a dialog item. The dialog item is a Text item, ComboBox, or other
suitable device specified in a resource (.r
) file.
A ListBox
is a familiar user interface dialog item used to present a list of data.
MicroStation ListBoxes
are multi-row and may have multiple columns.
The ListModel
is a multi-row, multi-column data structure.
One feature is the facility to assign a cell editor to a given column of a ListBox
.
A cell editor is another user interface device, such as a Text item or ComboBox.
When a user clicks a ListBox
cell that has been assigned an editor, the Dialog Manager pops a
temporary instance of the editor over the current ListBox
text.
The following paragraphs describe hook functions: a legacy way to define programmatically the behaviour of a UI widget. An alternative to hook functions is a Hook Handler class.
The C++ programmer sees a ListBox
as a complex device that requires a
hook function (also known as a callback function) to be used successfully.
The hook function usually handles the following events …
Create a ListModel
and assign it to your ListBox
in the
DITEM_MESSAGE_CREATE
event. In this code fragment, the function
createListModel()
populates the ListModel
.
The ListBox
structure includes
a member to store the pointer to its ListModel
.
In the DITEM_MESSAGE_DESTROY
event, retrieve the pointer to the
ListModel
and destroy it …
case DITEM_MESSAGE_CREATE: pListModel = createListModel (nColumns); mdlDialog_listBoxSetListModelP (dimP->dialogItemP->rawItemP, pListModel, mdlListModel_getColumnCount (pListModel)); break; case DITEM_MESSAGE_DESTROY: pListModel = mdlDialog_listBoxGetListModelP (dimP->dialogItemP->rawItemP); mdlListModel_destroy (pListModel, TRUE); break;
The code fragment below shows how to get the row and column indices of the cell
that a user has clicked. Both indices start at zero, as you would expect in
a C language API. However, the row index may have special negative values
HEADER_ROW_INDEX
and FILTER_ROW_INDEX
(#define
d in <dlogitem.h>)
which are passed when the user clicks on the column header or filter area …
case DITEM_MESSAGE_BUTTON: if (BUTTONTRANS_UP == dimP->u.button.buttonTrans) { ListModelP pListModel { nullptr }; ListCellP pListCell { nullptr }; int row, col;// Get cell coordinate on single- or double-click
if (SUCCESS == mdlDialog_listBoxLastCellClicked (&row, &col, dimP->dialogItemP->rawItemP)) { pListModel = mdlDialog_listBoxGetListModelP (dimP->dialogItemP->rawItemP); pListCell = mdlListModel_getCellAtIndexes (pListModel, row, col); } if ((1 == dimP->u.button.upNumber) && dimP->u.button.clicked) {// Action on single-click
} else if ((2 == dimP->u.button.upNumber) && dimP->u.button.clicked) {// Action on double-click
} } break;
The code fragment below shows how to get the row and column indices of the cell that a user has clicked …
case DITEM_MESSAGE_STATECHANGED:
{
ListRowP pRow { nullptr };
ListModelP pListModel { mdlDialog_listBoxGetListModelP (pLBox) };
int nRows { mdlListModel_getRowCount (pListModel) };
if (!dimP->u.stateChanged.reallyChanged)
break;
mdlDialog_listBoxLastCellClicked (&m_nRow, &m_nCol, pLBox);
if (pListModel && 0 < nRows)
{
YOUR_STRUCT_PTR pData = NULL;
pRow = mdlListModel_getRowAtIndex (pListModel, m_nRow);
// Your data pointer is stored in application data of ListRow
pData = (YOUR_STRUCT_PTR)mdlListRow_getAppData (pRow);
}
break;
}
A
The function below creates a ListModel
object and adds six rows. Each row
has a predetermined number of columns. In this example we assign each cell in the
row a text value that is the row & column index of that cell in the model.
ListModelP CreateListModel ( int nCols ) { ListModelP pListModel { mdlListModel_create (nCols) }; int rowIndex; int colIndex; ASSERT (NULL != pListModel); const int& RowCount { 6 }; for (rowIndex = 0; rowIndex != RowCount; ++rowIndex) { ListRowP pRow { mdlListRow_create (pListModel) }; ASSERT (NULL != pRow); for (colIndex = 0; colIndex != nCols; ++colIndex) { const int& LongEnough { 32 }; WChar value [LongEnough]; swprintf (value, L"value %d.%d", rowIndex, colIndex); ListCellP pCell { mdlListRow_getCellAtIndex (pRow, colIndex) }; const bool& DisplayText { true }; mdlListCell_setStringValue (pCell, value, DisplayText); } mdlListModel_addRow (pListModel, pRow); } return pListModel; }
A ListCell
is a versatile container for different data types:
a ListCell
can store numbers, Unicode strings, or C multibyte strings.
It can also store data passed from an C++ ValueDescr
structure.
To store data in a ListCell
, use mdlListCell_setXxx
functions like these …
mdlListCell_setLongValue mdlListCell_setDoubleValue mdlListCell_setStringValue mdlListCell_setValue
What a ListCell
displays in a ListBox
or ComboBox
is distinct
from its data content.
In other words, you can tell a ListCell
to display something quite different to its data.
The value of separating display from content is data presentation:
you can choose to format the ListCell
's data.
For example, suppose you want to store a double
value that represents currency.
The currency should be displayed in a human-digestible format.
Let's say the data value is 1234.567, but you want it displayed as US dollars with a comma separating the thousands,
and not more than two decimal points.
The ListCell
display should be $1,234.57.
You can set the display of a ListCell
using mdlListCell_setDisplayText
.
It is perfectly OK to assign a numeric value to a ListCell
, then set its display text like this …
long value = 123456; ListCellP pCell = …; WChar formatted [32]; mdlListCell_setLongValue (pCell, value); yourFunc_formatInteger (formatted, value); mdlListCell_setDisplayText (pCell, formatted);
Here's a link to information on creating a localised format.
Sorting a ListModel
is two-step process …
ListModel
column
ListModel
to sort itself
Assign a sort function using mdlListColumn_setSortFunction
.
Lookup that function in C++ help to see the sort function signature and an example.
Note that you can optionally specify a secondary sort column. A secondary sort column is used when two rows in the primary sort column are equal.
Primary Sort Column | Secondary Sort Column |
---|---|
… | … |
Smith | Susan |
… | … |
Smith | John |
… | … |
For example, suppose you have a primary sort column second name and a secondary sort column first name.
Suppose that your ListModel
contains values Smith, John and Smith, Susan.
When sorting the primary columns having value Smith, the sort uses the secondary column
to sort the rows having values John and Susan.
Use mdlListModel_sort
to perform the sort.
Often, you will call this function from a ListBox hook function,
when a user clicks on a ListBox column.
While building a ListModel
, as shown above, you have
detailed access to each row as it is created, and you can obtain a reference to each column in the
row as a ListCell
reference. The best place to assign a cell editor is while you
are building the ListModel
.
With a reference to a ListCell
, you assign a dialog item appropriate to the type
of data that you want to edit. For arbitrary text, assign a Text item; use a
ComboBox to let the choose pick a Yes/No value or one of a short list of values;
choose a ColorPicker to let the user choose a colour.
You assign an editor by specifying its ID in your resource (.r
) file.
Whichever editor you assign, its item definition must exist in the resource file.
For example, suppose you have this Text item definition in your resource file …
DItem_TextRsc TEXTID_UserInfo = { NOCMD, LCMD, NOSYNONYM, NOHELP, MHELP, HOOKITEMID_MyHookForText, NOARG, 25, "%s", "%s", "", "", NOMASK, NOCONCAT, "g_dataVars.stringVal", "g_dataVars.stringVal" };
You assign this Text item with a call to mdlListCell_setEditor()
.
This function tells the Dialog Manager what type of item you want to assign, its ID, and
the C++ task that owns the item (usually your own C++ application) …
mdlListCell_setEditor (pCell, RTYPE_Text, TEXTID_UserInfo, mdlSystem_getCurrMdlDesc (), false, true);
Here's the earlier createListModel (nColumns) example modified to assign a Text item as editor for column four …
ListModelP CreateListModel ( int nCols ) { ListModelP pListModel { mdlListModel_create (nCols) }; ASSERT (NULL != pListModel); const int& RowCount { 6 }; for (int rowIndex = 0; rowIndex != RowCount; ++rowIndex) { ListRowP pRow { mdlListRow_create (pListModel) }; ASSERT (NULL != pRow); for (int colIndex = 0; colIndex != nCols; ++colIndex) { const int& LongEnough { 32 }; WChar value [LongEnough]; swprintf (value, L"value %d.%d", rowIndex, colIndex); ListCellP pCell { mdlListRow_getCellAtIndex (pRow, colIndex) }; const bool& SetDisplayText { true }; mdlListCell_setStringValue (pCell, value, SetDisplayText);// Assign a cell editor to column 4
switch (colIndex) { case 3:// Column 4 shows plain text with text editor
const bool& EditDisplay { false }; const bool& UpdateDisplay { false }; mdlListCell_setEditor (pCell, RTYPE_Text, TEXTID_UserInfo, mdlSystem_getCurrMdlDesc (), EditDisplay, UpdateDisplay); break; } mdlListModel_addRow (pListModel, pRow); } return pListModel; }
ComboBox
and ListBox
dialog items require an item hook function or class
to modify their default behaviour.
This article about a
hook class base template
shows you how to manage a ListModel
in your hook class.
Return to MicroStationAPI articles index.
Post questions about C++ and the MicroStationAPI to the MicroStation Programming Forum.