Page Contents
Questions similar to this appear on the Bentley Discussion Groups. These questions, or something like them, appeared in the MDL discussion group.
Q Questions about ListModels pop up occasionally …
ListModel? ListModel Cell Editor? A
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.
As an MDL 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 interface item used to present a list of data.
MicroStation ListBoxes are multi-row and may have multiple columns.
The StringList, as its name suggests, is a list of variable-length char* strings.
Each member of the list has zero or more optional numeric data fields.
MicroStation/J used a StringList as the data source for a ListBox.
You can continue to use a StringList in V8.
It's useful for porting code from V7 to V8, but for new projects you should consider the ListModel.
The ListModel is a multi-row, multi-column data structure.
It is more flexible and provides more features than a StringList.
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 MDL programmer sees a ListBox as a complex device that requires an MDL
hook 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)
{
ListModel* pListModel = NULL;
ListCell* pListCell = NULL;
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:
{
ListRow* pRow = NULL;
ListModel* 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;
}
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.
ListModel* createListModel
(
int nCols
)
{
ListModel* pListModel = NULL;
ListRow* pRow = NULL;
int rowIndex;
int colIndex;
pListModel = mdlListModel_create (nCols);
ASSERT (NULL != pListModel);
for (rowIndex = 0; rowIndex < 6; ++rowIndex)
{
pRow = mdlListRow_create (pListModel);
ASSERT (NULL != pRow);
for (colIndex = 0; colIndex < nCols; ++colIndex)
{
ListCell* pCell = NULL;
char value [32];
sprintf (value, "value %d.%d", rowIndex, colIndex);
pCell = mdlListRow_getCellAtIndex (pRow, colIndex);
mdlListCell_setStringValue (pCell, value, TRUE);
}
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 MDL ValueDescr structure.
To store data in a ListCell, use mdlListCell_setXxx functions like these …
mdlListCell_setLongValue mdlListCell_setDoubleValue mdlListCell_setStringValue mdlListCell_setStringValueW 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 or mdlListCell_setDisplayTextW.
It is perfectly OK to assign a numeric value to a ListCell, then set its display text like this …
long value = 123456; ListCell* pCell = …; char 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.
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 MDL task that owns the item (usually your own MDL 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 …
ListModel* createListModel
(
int nCols
)
{
ListModel* pListModel = NULL;
ListRow* pRow = NULL;
int rowIndex;
int colIndex;
pListModel = mdlListModel_create (nCols);
ASSERT (NULL != pListModel);
for (rowIndex = 0; rowIndex < 6; ++rowIndex)
{
pRow = mdlListRow_create (pListModel);
ASSERT (NULL != pRow);
for (colIndex = 0; colIndex < nCols; ++colIndex)
{
ListCell* pCell = NULL;
char value [32];
sprintf (value, "value %d.%d", rowIndex, colIndex);
pCell = mdlListRow_getCellAtIndex (pRow, colIndex);
mdlListCell_setStringValue (pCell, value, TRUE);
// Assign a cell editor to column 4
switch (colIndex)
{
case 3:
// Column 4 shows plain text with text editor
mdlListCell_setEditor (pCell, RTYPE_Text, TEXTID_UserInfo, mdlSystem_getCurrMdlDesc (), FALSE, TRUE);
break;
}
mdlListModel_addRow (pListModel, pRow);
}
return pListModel;
}
The code fragments here illustrate particular points about ListModel code.
They are not intended to provide a working example.
However, there is a complete example MDL project Datasheet that uses similar
code in a complete working application.
The code in Datasheet is based on an original example supplied by Bentley Systems, Inc., to which we've added extra code, including that which assigns a distinct cell editor to each of several columns. The additional code illustrates the use of a Text item, a ComboBox item, and a ColorPicker item.
Return to MDL articles index.