SDK:Working with Hierarchical VWListBrowserCtrl

From Vectorworks Developer
Jump to: navigation, search

.SDK|SDK ..SDK:Types|SDK Types ..SDK:Using the SDK|Using the SDK ..VCOM:VCOM (Vectorworks Component Object Model)|VCOM Basics ..VCOM:Class Reference|VCOM Class Reference


Since Vectorworks 2013 it is possible to create hierarchical list browsers ie with rows which can be expanded to reveal other rows, recursively.

Set up : example

A hierarchical list browser in Vectorworks

We will try in this article to get the result shown above.

Basic concept : the '-' character

First thing to understand with hierarchical list browsers is that you don't insert every rows shown in the result. In our example, the inserted rows in the list browser are :

abcd-first row
abcd-2nd row
abcd-3rd row
abcd-4th row
EFGH-1st row
EFGH-2nd row
EFGH-3rd row-AA
EFGH-3rd row-BB
EFGH-3rd row-CC

This is exactly the way the class list browser is working in the Organization dialog.

How to activate the hierarchical mode

// activating hierarchical mode
gSDK->EnableListBrowserHierarchicalDisplay( GetDialogID(), GetControlID(), true );

// telling LB which column is the one with collapse/expand arrow picture
gSDK->SetListBrowserHierarchicalDisplayColumn( GetDialogID(), GetControlID(), kFirstColumnIndex );

// we need to put these attributes. It doesn't work without them
gSDK->SetListBrowserItemDisplayType( GetDialogID(), GetControlID(), kFirstColumnIndex, kListBrowserDisplayImageAndText );
gSDK->SetListBrowserEditDisplayType( GetDialogID(), GetControlID(), kFirstColumnIndex, kListBrowserDisplayImageAndText );
gSDK->SetListBrowserControlType( GetDialogID(), GetControlID(), kFirstColumnIndex, kListBrowserControlDiscTriangle);

Adding rows

size_t row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "abcd-first row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "abcd-2nd row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "abcd-3rd row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "abcd-4th row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "EFGH-1st row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "EFGH-2nd row" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "EFGH-3rd row-AA" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "EFGH-3rd row-BB" );
row = AddRow( "" );
GetItem( row, kFirstColumnIndex ).SetItemText( "EFGH-3rd row-CC" );

The missing mistery part

Once we've done all this, the list browser looks like this : something's wrong

Not very hirerarchical isn't it ? It's because one of the calls we've done before the row inserts has to be made after. So we just need to do this :

// activating hierarchical mode (again !)
gSDK->EnableListBrowserHierarchicalDisplay( GetDialogID(), GetControlID(), true );

Dealing with events

When the user clicks on the expand/collapse arrow, he expects to expand or collapse the tree below the clicked row. We have to deal with this.

size_t		row, col;
EListBrowserEventType type = eventArgs.GetListBrowserEvent(row, col);

if(type == kListBrowserEventType_DataChangeClick)     // arrow click
	if(gSDK->HierarchicalListBrowserItemIsClosed( GetDialogID(), Di_LP_ListItem, row ))
		gSDK->HierarchicalListBrowserItemOpened( GetDialogID(), Di_LP_ListItem, row, false, processedItemsCnt);
		gSDK->GetDisplayedItemsCountInHierarchicalContainer( GetDialogID(), Di_LP_ListItem, row, processedItemsCnt);
		gSDK->HierarchicalListBrowserItemClosed( GetDialogID(), Di_LP_ListItem, row, false );


When the user closes a row, the list browser looses all the data attached to hidden rows except the text of the hierarchical column. All the other columns, styles, item data, etc, are lost. We have to restore all these when the user opens back the row.

Vectorworks 2019: It looks like the above is not true anymore as the ListBrowser restores the content automatically.

It is useful to know that getting a cell content will return partial string, as it appears on the ListBrowser. To get the full row string, including the parent folders and delimiter, use:

TXString cellTxt = VWListBrowserCtrl::GetItemOriginalName( row, true );

VWFC Example

Here is a full VWFC example. It shows a hierarchical ListBrowser control containing a set of services, ordered in folders. It is used in a dialog that asks the user to select a service:

void CDlgSelService::OnInitializeContent()

    VWListBrowserCtrl* servicesLB = this->GetListBrowserCtrlByID( kServiceLB );
    servicesLB->EnableHierarchicalDisplay( true );
    VWListBrowserColumn nameCol = servicesLB->AddColumn( kStr_ColName_ServiceName, "kStr_ColName_ServiceName", 280 );
    nameCol.SetEditDisplayType( kListBrowserDisplayImageAndText );
	nameCol.SetItemDisplayType( kListBrowserDisplayImageAndText );
    nameCol.SetColumnType( kListBrowserControlDiscTriangle );
    // ...

void CDlgSelService::FillServices()
    VWListBrowserCtrl* servicesLB = this->GetListBrowserCtrlByID( kServiceLB );
    const auto& serversList = Data::CServersList::Instance();
    const auto& arrServers = serversList.GetServers();
    fWebContext.SetServerURL( fServerPopup < arrServers.size() ? arrServers[fServerPopup].fURL : serversList.GetDefaultURL() );
    TXStringSTLArray    arrServices;
    fListServices.ListServices( arrServices );
    std::sort( arrServices.begin(), arrServices.end(), [](const TXString& a, const TXString& b) { return a < b; } );
    size_t selService = size_t(-1);
    for(const auto& service : arrServices)
        TXString line = service;
        line.Replace( "/", "-" );
        size_t rowIndex = servicesLB->AddRow( line );
        if ( service == fSelectedService )
            selService = rowIndex;
    servicesLB->EnableHierarchicalDisplay( true );
    if ( selService != size_t(-1) )
        servicesLB->SelectRow( selService, true );

void CDlgSelService::OnServiceLB(TControlID controlID, VWDialogEventArgs& eventArgs)
    VWListBrowserCtrl* servicesLB = this->GetListBrowserCtrlByID( kServiceLB );
    size_t        row, col;
    EListBrowserEventType type = eventArgs.GetListBrowserEvent( row, col );
    if ( col == 0 && type == kListBrowserEventType_DataChangeClick )     // arrow click
        size_t processedItemsCnt = 0;
        bool doExpand = servicesLB->IsRowClosed( row );
        if ( doExpand )
            servicesLB->ExpandItems( row, false, processedItemsCnt );
            servicesLB->CloseItems( row, false, processedItemsCnt );
    else if ( type == kListBrowserEventType_SelectionChangeClick || type == kListBrowserEventType_DoubleClick )
        VWListBrowserCtrl* servicesLB = this->GetListBrowserCtrlByID( kServiceLB );
        size_t selServiceIndex = servicesLB->GetFirstSelected();
        if ( selServiceIndex != size_t(-1) )
            fSelectedService = servicesLB->GetItemOriginalName( selServiceIndex, true );
            fSelectedService.Replace( "-", "/" );


        if ( type == kListBrowserEventType_DoubleClick )
            VWDialog::SetDialogClose( true );

See Also

SDK:Working with VWListBrowserCtrl | SDK:Layout Manager: List Browser

Personal tools

Advanced Search
See Also