Questions similar to this appear on the MicroStation Programming Forum.

Q Questions about ListModels pop up occasionally …

DataSheet Project

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.

DataSheet Dialog

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 and ListModels

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 ListRows. Each ListRow contains one or more ListColumns. 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.

ListModel

The header file for ListModels 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.

ListBoxes

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 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 (#defined 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;
}

How to Populate a ListModel

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

ListCell Data

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

ListCell Display

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.

How to Sort a ListModel

Sorting a ListModel is two-step process …

  1. Assign a sort function to a ListModel column
  2. Tell the ListModel to sort itself

Set Column Sort Function

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.

Sort the ListModel

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.

How to specify a ListModel Cell Editor

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, C++System_getCurrC++Desc (), 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;
}

How to Manage a ListModel in your Dialog Item hook class

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.