Click here to Skip to main content
15,880,543 members
Articles / Desktop Programming / X11

Unleash the power of list and tree widget (C# X11)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
13 Aug 2014CPOL17 min read 11.5K   150   4  
How to get out the maximum from the Roma Widget Set's list and tree widgets in C#.

Introduction

This article is focused on the two widgets XrwList and XrwTree from the Roma Widget Set (Xrw). The Roma Widget Set is a zero dependency GUI application framework for X11 (it requires only assemblies of the free Mono standard installation and libraries of the free X11 distribution; it doesn't particularly require GNOME, KDE or commercial libraries) and is implemented entirely in C#.

Please find the concepts and general descriptions in Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - part 1, Basics. It should always be the preferred source.

The reference description of simple widgets (this is where XrwList, XrwTree and XrwViewportGridViewHeader belong to) is to find in Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - part 2, Simple widgets.

The reference description of composite widgets (this is where XrwViewport belongs to) is to find in Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - part 3, Composite widgets.

Background

The XrwList and XrwTree widgets deliver their full performance only in cooperation with the XrwViewport and XrwViewportGridViewHeader widgets. How to apply this widget combination is, where this article goes beyond the reference description of the single widgets. This covers among other things:

  • text view, large icon view, small icon view, list view and advanced (multi column) view for XrwList
  • list view item selection notification for XrwList
  • automatic detection of the demand to show scrollbar(s), demand independend always visible or always hidden scrollbars for XrwViewport with XrwList and XrwList
  • singel and multi selection for XrwList
  • text view and advanced (multi column) view for XrwTree
  • column header display for XrwList and XrwTree using XrwViewportGridViewHeader inside XrwViewport
  • column resizing and reordering for XrwList and XrwTree using XrwViewportGridViewHeader inside XrwViewport
  • in-place editing for XrwList and XrwTree (including information about the editor invocation)

Using the code

The sample application was written with Mono Develop 2.4.1 for Mono 2.8.1 on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. Neither the port to any older nor to any newer version should be a problem. The sample application's solution consists of one project containing all the necessary source code.

The sample application is also tested with Mono Develop 3.0.6 for Mono 3.0.4 on OPEN SUSE 12.3 Linux 64 bit DE and GNOME desktop, IceWM, TWM und Xfce.

The Xlib/X11 window handling is based on the X11Wrapper assembly version 0.5, that defines the function prototypes, structures and types for Xlib/X11 calls to the libX11.so. It has been developed for the Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept) project and has been advanced during the Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Part 1, Basics project.

The sample application implements several levels of complexity and functionality for XrwList and XrwTree widgets.

Image 2

To play with the executables start either /bin/Debug/32/UnleshListAndTree.exe on 32 bit systems or /bin/Debug/64/UnleshListAndTree.exe on 64 bit systems.

To load the project use either the UnleshListAndTree32.sln on 32 bit systems or the UnleshListAndTree.sln on 64 bit systems.

Let's start with some XrwList samples and continue with XrwTree later on.

XrwList samples

Just text, no scroll

The most simple implementation of XrwList is to use it without XrwViewport and XrwViewportGridViewHeader and to display static text only. This approach, to omit a surrounding XrwViewport, is not recommended - except the minimum size of the XrwList guarantees the complete display of all list items.

The sample code shows how to define the list widget (with the minimum of recommended calls) and to connect the EntrySelectionChanged event's anonymous delegate.

C++
// Define the list.
XrwList list1    = XrwList.NewListWidget (parent);
list1.ExpandToAvailableHeight = true;
list1.ExpandToAvailableWidth  = true;
parent.AddChild (list1);

// Be notified about selection change.
list1.EntrySelectionChanged += delegate(XrwRectObj source, XrwList.ListItem entry)
{
    IGridViewElement item = list1.SelectedItem ();
    string selection  = "Selected item(s): ";
    int index = -1;
    if (item != null && (index = list1.IndexOf (item)) >= 0)
        selection += index.ToString ();
    else
        selection += "-";
    // Tip --- define: labelList1Feedback.ExpandToAvailableWidth = true;
    labelList1Feedback.Label = selection;
    labelList1Feedback.InvokeRedraw ();
}; 

The (invisible) default column of the XrwList widget is created automatically during construction and the appropriate MeasureCellMeasureCellEditorOffset and DrawCell handler are connected to their fallback delegate implementations.

The labelList1Feedback gadget is just to display the selection result in order to check the EntrySelectionChanged delegate functionality. The gadget displays the text "Selected item(s):" followed by the selected item's index.

The next sample code shows how to create list items displaying static text only. The static text (first parameter of the XrwList.ListItem's constructor) is used to initialize the XrwList.ListItem's Data  property directly. All XrwList.ListItem's are added to the XrwList's Items collection.

C++
// Define the list items.
XrwList.ListItem ln1A = new XrwList.ListItem ("Item one List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1A);
XrwList.ListItem ln1B = new XrwList.ListItem ("Item two List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1B);
XrwList.ListItem ln1C = new XrwList.ListItem ("Item three List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1C);
XrwList.ListItem ln1D = new XrwList.ListItem ("Item four List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1D);
XrwList.ListItem ln1E = new XrwList.ListItem ("Item fife List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1E);
XrwList.ListItem ln1F = new XrwList.ListItem ("Item six List-item with a simple text only.",
                                              null, false, null, false);
list1.Items.Add (ln1F);
...

That's all to show a simple list like this:

Image 3

Large icon view, small icon view and list view with scroll

The next more advanved implementation of XrwList is to use it with surrounding XrwViewport and to display icon and static text per list item.

The next sample code shows how to define the viewport and list widgets (with the minimum of recommended calls) for a large icon view and to connect the EntrySelectionChanged event's anonymous delegate. The delegate now supports single and multi selection.

C++
// Define the viewport.
XrwViewport viewportList2 = XrwViewport.NewViewportGadget (parent);
viewportList2.ExpandToAvailableWidth  = true;
viewportList2.ExpandToAvailableHeight = true;
viewportList2.ForceBars = false;
parent.AddChild (viewportList2);
// Define the list.
XrwList list2    = XrwList.NewListWidget (viewportList2);
list2.ExpandToAvailableHeight = true;
list2.ExpandToAvailableWidth  = true;
list2.View = ViewType.LargeIcon;
viewportList2.AddChild (list2);

// Be notified about selection change.
list2.EntrySelectionChanged += delegate(XrwRectObj source, XrwList.ListItem entry)
{
    if (list2.MultiSelect == true)
    {
        List<IGridViewElement> items = list2.SelectedItems ();
        string selection  = "Selected item(s): ";
        bool   firstMatch = true;
        foreach (IGridViewElement item in items)
        {
            int index = list2.IndexOf (item);
            if (index >= 0)
            {
                selection += (firstMatch ? index.ToString () : ", " + index.ToString ());
                firstMatch = false;
            }
        }
        if (items.Count == 0)
            selection += "-";
        labelList2Feedback.Label = selection;
        labelList2Feedback.InvokeRedraw ();
    }
    else
    {
        IGridViewElement item = list2.SelectedItem ();
        string selection  = "Selected item(s): ";
        int index = -1;
        if (item != null && (index = list2.IndexOf (item)) >= 0)
            selection += index.ToString ();
        else
            selection += "-";
        labelList2Feedback.Label = selection;
        labelList2Feedback.InvokeRedraw ();
    }
};

The listView2.View = ViewType.LargeIcon sets the view type to large icon view.

The labelList2Feedback gadget is just to display the selection result in order to check the EntrySelectionChanged delegate functionality. The gadget displays the text "Selected item(s):" followed by the list if all selected item's indices, delimited by ",".

The next sample code shows how to manipulate the viewport and list behaviour. The anonymous delegates are connected to three radio buttons of one radio group and one toggle button. They realize the switch between auto displayed scroll bars, always displayed scroll bars and alwasy hidden scroll bars as well as between single selection and multi selection.

C++
// Viewport manipulation.
radioList2ScrollAuto.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = false;
    viewportList2.AllowHoriz = true;
    viewportList2.AllowVert = true;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};
radioList2ScrollAlways.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = true;
    viewportList2.AllowHoriz = true;
    viewportList2.AllowVert = true;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};
radioList2ScrollOff.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = false;
    viewportList2.AllowHoriz = false;
    viewportList2.AllowVert = false;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};

// Multi selection manipulation.
toggleMultiselect2.SwitchedOn += delegate(XrwRectObj source)
{
    list2.MultiSelect = true;
};
toggleMultiselect2.SwitchedOff += delegate(XrwRectObj source)
{
    list2.MultiSelect = false;
};

If multi selection capability is switched on, the selected list items must be determined using the XrwList.SelectedItems() method instead of the XrwList.SelectedItem(). To distinguish whether the multi selection capability is switched on, XrwList.MultiSelect property can be used.

The next sample code shows how to create list items displaying large icon and static text per list item. The static text (first parameter of the XrwList.ListItem's constructor) is used to initialize the XrwList.ListItem's Data  property directly.

C++
// Define the images.
X11Graphic informationLargeGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Information32);
X11Graphic questionLargeGraphic    = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Question32);
            
// Define the list items.
XrwList.ListItem ln2A = new XrwList.ListItem ("Item one", informationLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2A);
XrwList.ListItem ln2B = new XrwList.ListItem ("Item two", questionLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2B);
XrwList.ListItem ln2C = new XrwList.ListItem ("Item three", informationLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2C);
XrwList.ListItem ln2D = new XrwList.ListItem ("Item four", questionLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2D);
XrwList.ListItem ln2E = new XrwList.ListItem ("Item fife", informationLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2E);
XrwList.ListItem ln2F = new XrwList.ListItem ("Item six", questionLargeGraphic,
                                              true, null, false);
list2.Items.Add (ln2F);
...

This is what the more advanved large icon view list looks like:

Image 4

The code to define the viewport and list widgets for a small icon view and to connect the EntrySelectionChanged event's anonymous delegate is very similar to that one for large icon view.

The only difference is that XrwList.View property is set to ViewType.SmallIcon.

The code to manipulate the viewport behaviour is very similar to that one for large icon view and omitted here.

The next sample code shows how to create list items displaying small icon and static text per list item. The static text (first parameter of the XrwList.ListItem's constructor) is used to initialize the XrwList.ListItem's Data  property directly.

C++
// Define the images.
X11Graphic informationSmallGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Information16);
X11Graphic questionSmallGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                       X11Graphic.StockIcon.Question16);
// Define the list items.
XrwList.ListItem ln3A = new XrwList.ListItem ("Item one", null, false,
                                              informationSmallGraphic, true);
list3.Items.Add (ln3A);
XrwList.ListItem ln3B = new XrwList.ListItem ("Item two", null, false,
                                              questionSmallGraphic, true);
list3.Items.Add (ln3B);
XrwList.ListItem ln3C = new XrwList.ListItem ("Item three", null, false,
                                              informationSmallGraphic, true);
list3.Items.Add (ln3C);
XrwList.ListItem ln3D = new XrwList.ListItem ("Item four", null, false,
                                              questionSmallGraphic, true);
list3.Items.Add (ln3D);
XrwList.ListItem ln3E = new XrwList.ListItem ("Item fife", null, false,
                                              informationSmallGraphic, true);
list3.Items.Add (ln3E);
XrwList.ListItem ln3F = new XrwList.ListItem ("Item six", null, false,
                                              questionSmallGraphic, true);
list3.Items.Add (ln3F);
...

This is what the more advanved small icon view list looks like:

Image 5

The main difference between the small icon view and list view display of the list is the listView4.View = ViewType.List property value.

And this is what themore advanved large list view list looks like:

Image 6

Viewport scroll and single / multi selection

The large icon view, small icon view and list view samples provide a radio group to switch the XrwList's surrounding XrwViewport scroll capabilities between automatic scrollbar display, always forced scrollbar display and always hidden scroll bars. The code to connect the SwitchedOn event's anonymous delegate has already been shown for the large icon sample:

C++
// Viewport manipulation.
radioList2ScrollAuto.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = false;
    viewportList2.AllowHoriz = true;
    viewportList2.AllowVert = true;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};
radioList2ScrollAlways.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = true;
    viewportList2.AllowHoriz = true;
    viewportList2.AllowVert = true;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};
radioList2ScrollOff.SwitchedOn += delegate(XrwRectObj source)
{
    viewportList2.ForceBars = false;
    viewportList2.AllowHoriz = false;
    viewportList2.AllowVert = false;
    viewportList2.CalculateChildLayout (viewportList2.AssignedPosition, viewportList2.AssignedSize);
};

The large icon view, small icon view and list view samples provide as well a toggle to switch the XrwList's multi selection capability on or off. The code to connect the SwitchedOn and SwitchedOff event's anonymous delegate has already been shown for the large icon sample:

C++
// Multi selection manipulation.
toggleMultiselect2.SwitchedOn += delegate(XrwRectObj source)
{
    list2.MultiSelect = true;
};
toggleMultiselect2.SwitchedOff += delegate(XrwRectObj source)
{
    list2.MultiSelect = false;
};

Column resizing and reordering

Multi column list views, that display column headers, can resize and reorder their columns. To use these features, the XrwViewport's HeaderVisibility property must be set to Visibility.Visible and the XrwViewport's HeaderPreferredHeight property must be set to an appropriate value. Column resizing is always enabled. To enable column reordering, the XrwViewport contained XrwViewportGridViewHeader's ColumnDrag property must be set to either ColumnDragType.HeaderAnimated or ColumnDragType.FullAnimated.

The sample code shows how to create list items for the multi column list. Now the first parameter of the XrwList.ListItem's constructor is not a static text but a GridViewNodeData instance. The GridViewNodeData is a sample implementation of a data object, that is prepared for the use with XrwList/XrwTree. The main aspect of the preparation is the provision of public properties, that can be bound to XrwList/XrwTree columns.

C++
// Define the list items.
XrwList.ListItem ln1A = new XrwList.ListItem (new GridViewNodeData ("Item one. Multi-column with a small " +
                                              "icon.", true, ThreeState.on, TArrowOrientation.Up, "Item one."),
                                              null, false, informationSmallGraphic, true);
ln1A.Editable = true;
list1.Items.Add (ln1A);
XrwList.ListItem ln1B = new XrwList.ListItem (new GridViewNodeData ("Item two. Multi-column with a small " +
                                              "icon.", false, ThreeState.off, TArrowOrientation.Down, "Item two."),
                                              null, false, questionSmallGraphic, true);
ln1B.Editable = true;
list1.Items.Add (ln1B);
XrwList.ListItem ln1C = new XrwList.ListItem (new GridViewNodeData ("Item three. Multi-column with a small " +
                                              "icon.", true, ThreeState.unset, TArrowOrientation.Left, "Item three."),
                                              null, false, informationSmallGraphic, true);
ln1C.Editable = true;
list1.Items.Add (ln1C);
XrwList.ListItem ln1D = new XrwList.ListItem (new GridViewNodeData ("Item four. Multi-column with a small " +
                                              "icon.", false, ThreeState.on, TArrowOrientation.Right, "Item four."),
                                              null, false, questionSmallGraphic, true);
ln1D.Editable = true;
list1.Items.Add (ln1D);
XrwList.ListItem ln1E = new XrwList.ListItem (new GridViewNodeData ("Item fife. Multi-column with a small " +
                                              "icon.", true, ThreeState.off, TArrowOrientation.Up, "Item fife."),
                                              null, false, informationSmallGraphic, true);
ln1E.Editable = true;
list1.Items.Add (ln1E);
XrwList.ListItem ln1F = new XrwList.ListItem (new GridViewNodeData ("Item six. Multi-column with a small " +
                                              "icon.", false, ThreeState.unset, TArrowOrientation.Down, "Item six."),
                                              null, false, questionSmallGraphic, true);
ln1F.Editable = true;
list1.Items.Add (ln1F);
XrwList.ListItem ln1G = new XrwList.ListItem (new GridViewNodeData ("Item seven. Multi-column with a small " +
                                              "icon.", true, ThreeState.on, TArrowOrientation.Left, "Item seven."),
                                              null, false, informationSmallGraphic, true);
ln1G.Editable = true;
...

The next sample code shows how to adopt the default column (Columns[0]) and to define further columns suitable for the data types used by the list. The column data binding is done according to GridViewNodeData's properties Name, Col2, Col3, Col4 and Col5.

C++
// Configure and define the list columns.
list1.Columns[0].DisplayMemberBinding = new XrwBinding ("Name");
list1.Columns[0].Header = new XrwGridViewColumnHeader ("Name");
list1.Columns[0].Editable = true;
XrwGridViewColumn col2 = new XrwGridViewColumn (XrwList.MeasureCommonCell,
                                                XrwList.MeasureCommonCellEditorOffset, XrwList.DrawCommonCell);
col2.DisplayMemberBinding =  new XrwBinding ("Col2");
col2.Header = new XrwGridViewColumnHeader ("Boolean");
col2.Header.TextColor = list1.TextColor;
col2.Editable = true;
list1.Columns.Add (col2);
XrwGridViewColumn col3 = new XrwGridViewColumn (XrwList.MeasureCommonCell,
                                                XrwList.MeasureCommonCellEditorOffset, XrwList.DrawCommonCell);
col3.DisplayMemberBinding =  new XrwBinding ("Col3");
col3.Header = new XrwGridViewColumnHeader ("ThreeState");
col3.Header.TextColor = list1.TextColor;
col3.Editable = true;
list1.Columns.Add (col3);
XrwGridViewColumn col4 = new XrwGridViewColumn (XrwList.MeasureCommonCell,
                                                XrwList.MeasureCommonCellEditorOffset, XrwList.DrawCommonCell);
col4.DisplayMemberBinding =  new XrwBinding ("Col4");
col4.Header = new XrwGridViewColumnHeader ("Enum");
col4.Header.TextColor = list1.TextColor;
col4.Editable = true;
list1.Columns.Add (col4);
XrwGridViewColumn col5 = new XrwGridViewColumn (XrwList.MeasureCommonCell,
                                                XrwList.MeasureCommonCellEditorOffset, XrwList.DrawCommonCell);
col5.DisplayMemberBinding =  new XrwBinding ("Col5");
col5.Header = new XrwGridViewColumnHeader ("Text");
col5.Header.TextColor = list1.TextColor;
col5.Editable = true;
list1.Columns.Add (col5);             

A column creation always registers three handler,

  • one to calculate a cell's required size,
  • one to calculate a cell's editor offset and
  • one to draw the cell's content.

The default list column (Columns[0]) is always created registering the XrwList.MeasureCommonCell(), the XrwList.MeasureCommonCellEditorOffset() and the XrwList.DrawCommonCell() delegates to the handler. Because the columns 2 to 5 use data types, that have built-in support (boolean, tree state, enum, and text) for cell measurement, editor offset measurement and drawing, all of them register the XrwList.MeasureCommonCell(), the XrwList.MeasureCommonCellEditorOffset() and the XrwList.DrawCommonCell() delegates to the handler as well.

If there are inidividual data types used for the data object's properties, bound to columns, it might be required to register individual delegats to do cell measurement, editor offset measurement and drawing.

The next sample code shows how to manipulate the viewport and list behaviour. The anonymous delegates are connected to three radio buttons of one radio group and one toggle button. They realize the switch between disabled column dragging, header animated column dragging and full animated header dragging as well as between single selection and multi selection.

C++
// Viewport and list manipulation.
radioList1DragNever.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportList1.Header != null)
        viewportList1.Header.ColumnDrag = ColumnDragType.Never;
};
radioList1DragHeaderUpdate.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportList1.Header != null)
        viewportList1.Header.ColumnDrag = ColumnDragType.HeaderAninated;
};
radioList1DragFullUpdate.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportList1.Header != null)
        viewportList1.Header.ColumnDrag = ColumnDragType.FullAnimated;
};
toggleMultiselect1.SwitchedOn += delegate(XrwRectObj source)
{
    list1.MultiSelect = true;
};
toggleMultiselect1.SwitchedOff += delegate(XrwRectObj source)
{
    list1.MultiSelect = false;
};

This is what the most advanved multi column detail view list looks like:

Image 7

To use column resizing, the XrwViewport contained XrwViewportGridViewHeader widget must be shown (XrwViewpor.HeaderVisibility is set to Visibility.Visible).

The readyness to resize a column is indicated visually to the user by a cursor change to a horizontal double arrow at every column's right delimiter.

Image 8

Currently the sensitive area to resize a column is in the range of 5 pixels left and right to a column's right delimiter.

Image 9

To resize a column, the horizontal double arrow cursor must be dragged left or right and dropped at the target position.

To use column reordering, a multi column list is required and the XrwViewport contained XrwViewportGridViewHeader widget's ColumnDrag property must be set to either ColumnDragType.HeaderAnimated or ColumnDragType.FullAnimated.

If the XrwViewport contained XrwViewportGridViewHeader widget's ColumnDrag property is set to ColumnDragType.Never, outside the sensitive area to resize a column the cursor changes to an arrow.

Image 10

If the XrwViewport contained XrwViewportGridViewHeader widget's ColumnDrag property is set to either ColumnDragType.HeaderAnimated or ColumnDragType.FullAnimated, outside the sensitive area to resize a column the cursor changes to hand 2 and indicates it's readyness to reorder a column visually to the user.

Image 11

Currently the sensitive area to place a column to reorder is in the range of 5 to 15 pixels left and right to its column delimiters.

Image 12

To reorder a column, the hand 2 cursor must be dragged left or right and dropped at the target position. If the hand 2 cursor is moved into the sensitive area to reorder a column, either the column header moves to the new order position (ColumnDragType.HeaderAnimated is set) or column header and list column content move to the new order position (ColumnDragType.FullAnimated is set).

The radio buttons no drag, header update and full update switch between the ColumnDragType.NeverColumnDragType.HeaderAnimated and ColumnDragType.FullAnimated behaviour.

XrwTree samples

Just text, no scroll

The most simple implementation of XrwTree is to use it without surrounding XrwViewport and to display static text only. This approach, to omit a surrounding XrwViewport, is not recommended - except the minimum size of the XrwTree guarantees the complete display of all tree nodes.

The sample code shows how to define the tree widget (with the minimum of recommended calls) and to connect the EntrySelectionChanged event's anonymous delegate.

C++
// Define the list.
XrwTree tree1    = XrwTree.NewTreeWidget (parent);
tree1.ExpandToAvailableHeight = true;
tree1.ExpandToAvailableWidth  = true;
parent.AddChild (tree1);

// Be notified about selection change.
tree1.EntrySelectionChanged += delegate(XrwRectObj source, List<IGridViewElement> oldEntries,
                                        IGridViewElement newEntry)
{
    IGridViewElement node = tree1.SelectedNode ();
    string     selection  = "Selected node path: ";
    if (node != null)
        selection += tree1.PathOf (node);
    else
        selection += "-";    
    labelTree1Feedback.Label = selection;
    labelTree1Feedback.InvokeRedraw ();
};

The (invisible) default column of the XrwTree widget is created automatically during construction and the appropriate MeasureCellMeasureCellEditorOffset and DrawCell handler are connected to their fallback implementation delegates.

The labelTree1Feedback gadget is just to display the selection result in order to check the EntrySelectionChanged delegate functionality. The gadget displays the text "Selected node path:" followed by the path of the selected node. The path of a node is the concatenation of the involved node's indices starting with the root node and ending with the leaf node, delimited by ":".

The next sample code shows how to create tree nodes displaying static text only. The static text (first parameter of the XrwTree.TreeNode's constructor) is used to initialize the XrwTree.TreeNode's Data  property directly. Some tree nodes have child nodes. All root nodes are added to the XrwTree's RootNodes collection. All child nodes are added to their parent XrwTree.TreeNode's Nodes collection.

C++
// Define the tree nodes.
XrwTree.TreeNode tn1A = new XrwTree.TreeNode ("Tree-node 1 with simple text only. Has two detail levels.",
                                              null, false, null, false);
tree1.RootNodes.Add (tn1A);
XrwTree.TreeNode tn1A1 = new XrwTree.TreeNode ("Tree-node 1.1 with simple text only. Has one detail level.",
                                               null, false, null, false);
tn1A.Nodes.Add (tn1A1);
XrwTree.TreeNode tn1A11 = new XrwTree.TreeNode ("Tree-node 1.1.1 with simple text only. Has no detail level.",
                                                null, false, null, false);
tn1A1.Nodes.Add (tn1A11);
XrwTree.TreeNode tn1A2 = new XrwTree.TreeNode ("Tree-node 1.2 with simple text only. Has one detail level.",
                                               null, false, null, false);
tn1A.Nodes.Add (tn1A2);
XrwTree.TreeNode tn1A21 = new XrwTree.TreeNode ("Tree-node 1.2.1 with simple text only. Has no detail level.",
                                                null, false, null, false);
tn1A2.Nodes.Add (tn1A21);
XrwTree.TreeNode tn1B = new XrwTree.TreeNode ("Tree-node 2 with simple text only. Has one detail level.",
                                              null, false, null, false);
tree1.RootNodes.Add (tn1B);
XrwTree.TreeNode tn1B1 = new XrwTree.TreeNode ("Tree-node 2.1 with simple text only. Has no detail level.",
                                               null, false, null, false);
tn1B.Nodes.Add (tn1B1);
XrwTree.TreeNode tn1B2 = new XrwTree.TreeNode ("Tree-node 2.2 with simple text only. Has no detail level.",
                                               null, false, null, false);
tn1B.Nodes.Add (tn1B2);
XrwTree.TreeNode tn1C = new XrwTree.TreeNode ("Tree-node 3 with simple text only. Has one detail level.",
                                              null, false, null, false);
tree1.RootNodes.Add (tn1C);
...

That's all to show a simple tree like this:

Image 13

Icon and expander with scroll

The next more advanved implementation of XrwTree is to use it with surrounding XrwViewport and to display icon and expander with static text per tree node.

The next sample code shows how to define the viewport and tree widget (with the minimum of recommended calls) to display a small icon and expander. The code to connect the EntrySelectionChanged delegate is the same as in the previous sample.

C++
// Define the viewport.
XrwViewport viewportTree2 = XrwViewport.NewViewportGadget (parent);
viewportTree2.ExpandToAvailableWidth  = true;
viewportTree2.ExpandToAvailableHeight = true;
viewportTree2.ForceBars = false;
parent.AddChild (viewportTree2);
// Define the tree.
XrwTree tree2    = XrwTree.NewTreeWidget (viewportTree2);
tree2.ExpandToAvailableHeight = true;
tree2.ExpandToAvailableWidth  = true;
tree2.ShowExpander = true;
viewportTree2.AddChild (tree2);

// Be notified about selection change.
tree2.EntrySelectionChanged += delegate(XrwRectObj source, List<IGridViewElement> oldEntries,
                                        IGridViewElement newEntry)
{
    IGridViewElement item = tree2.SelectedNode ();
    string selection  = "Selected item path: ";
    if (item != null)
        selection += tree2.PathOf (item);
    else
        selection += "-";    
    labelTree2Feedback.Label = selection;
    labelTree2Feedback.InvokeRedraw ();
}; 

The labelTree2Feedback gadget is just to display the selection result in order to check the EntrySelectionChanged delegate functionality. The gadget displays the text "Selected node path:" followed by the path of the selected node. The path of a node is the concatenation of the involved node's indices starting with the root node and ending with the selected node, delimited by ":".

The next sample code shows how to manipulate the viewport and tree behaviour. The delegates are connected to three radio buttons of one radio group and one toggle button. They realize the switch between auto displayed scroll bars, always displayed scroll bars and alwasy hidden scroll bars as well as between displayed expander and hidden expander.

C++
// Viewport manipulation.
// Viewport and tree manipulation.
radioTree2ScrollAuto.SwitchedOn += delegate(XrwRectObj source)
{
    viewportTree2.ForceBars = false;
    viewportTree2.AllowHoriz = true;
    viewportTree2.AllowVert = true;
    viewportTree2.CalculateChildLayout (viewportTree2.AssignedPosition, viewportTree2.AssignedSize);
};
radioTree2ScrollAlways.SwitchedOn += delegate(XrwRectObj source)
{
    viewportTree2.ForceBars = true;
    viewportTree2.AllowHoriz = true;
    viewportTree2.AllowVert = true;
    viewportTree2.CalculateChildLayout (viewportTree2.AssignedPosition, viewportTree2.AssignedSize);
};
radioTree2ScrollOff.SwitchedOn += delegate(XrwRectObj source)
{
    viewportTree2.ForceBars = false;
    viewportTree2.AllowHoriz = false;
    viewportTree2.AllowVert = false;
    viewportTree2.CalculateChildLayout (viewportTree2.AssignedPosition, viewportTree2.AssignedSize);
};
toggleTree2ShowExpander.SwitchedOn += delegate(XrwRectObj source)
{
    tree2.ShowExpander = true;
    tree2.InvokeRedraw ();
};
toggleTree2ShowExpander.SwitchedOff += delegate(XrwRectObj source)
{
    tree2.ShowExpander = false;
    tree2.InvokeRedraw ();
};

The next sample code shows how to create tree nodes displaying small icon and static text per tree node. The static text (first parameter of the XrwTree.TreeNode's constructor) is used to initialize the XrwTree.TreeNode's Data  property directly. Again some tree nodes have child nodes. All root nodes are added to the XrwTree's RootNodes collection. All child nodes are added to their parent XrwTree.TreeNode's Nodes collection.

C++
// Define the images.
X11Graphic informationSmallGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Information16);
X11Graphic questionSmallGraphic    = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Question16);
X11Graphic attentionSmallGraphic   = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                          X11Graphic.StockIcon.Attention16);

// Define the tree items.
XrwTree.TreeNode tn2A   = new XrwTree.TreeNode ("Tree-node 1.",     null, false, informationSmallGraphic, true);
tn2A.Editable = true;
tree2.RootNodes.Add (tn2A);
XrwTree.TreeNode tn2A1  = new XrwTree.TreeNode ("Tree-node 1.1 .",  null, false, questionSmallGraphic, true);
tn2A1.Editable = true;
tn2A.Nodes.Add (tn2A1);
XrwTree.TreeNode tn2A11 = new XrwTree.TreeNode ("Tree-node 1.1.1.", null, false, attentionSmallGraphic, true);
tn2A11.Editable = true;
tn2A1.Nodes.Add (tn2A11);
XrwTree.TreeNode tn2A2  = new XrwTree.TreeNode ("Tree-node 1.2 .",  null, false, questionSmallGraphic, true);
tn2A2.Editable = true;
tn2A.Nodes.Add (tn2A2);
XrwTree.TreeNode tn2A21 = new XrwTree.TreeNode ("Tree-node 1.2.1.", null, false, attentionSmallGraphic, true);
tn2A21.Editable = true;
tn2A2.Nodes.Add (tn2A21);
XrwTree.TreeNode tn2B   = new XrwTree.TreeNode ("Tree-node 2.",     null, false, informationSmallGraphic, true);
tn2B.Editable = true;
tree2.RootNodes.Add (tn2B);
XrwTree.TreeNode tn2B1  = new XrwTree.TreeNode ("Tree-node 2.1.",   null, false, questionSmallGraphic, true);
tn2B1.Editable = true;
tn2B.Nodes.Add (tn2B1);
XrwTree.TreeNode tn2B2  = new XrwTree.TreeNode ("Tree-node 2.2.",   null, false, questionSmallGraphic, true);
tn2B2.Editable = true;
tn2B.Nodes.Add (tn2B2);
XrwTree.TreeNode tn2C   = new XrwTree.TreeNode ("Tree-node 3 .",    null, false, informationSmallGraphic, true);
tn2C.Editable = true;
tree2.RootNodes.Add (tn2C);
...

This is what the more advanved icon and expander tree looks like:

Image 14

Column resizing and reordering

Multi column tree views, that display column headers, can resize and reorder their columns. To use these features, the XrwViewport's HeaderVisibility property must be set to Visibility.Visible and the XrwViewport's HeaderPreferredHeight property must be set to an appropriate value. Column resizing is always enabled. To enable column reordering, the XrwViewport contained XrwViewportGridViewHeader's ColumnDrag property must be set to either ColumnDragType.HeaderAnimated or ColumnDragType.FullAnimated.

The sample code shows how to create treee nodes for the multi column tree. Now the first parameter of the XrwTree.TreeNode's constructor is not a static text but a GridViewNodeData instance. The GridViewNodeData is a sample implementation of a data object, that is prepared for the use with XrwList/XrwTree. The main aspect of the preparation is the provision of public properties, that can be bound to XrwList/XrwTree columns.

C++
// Define the tree nodes.
XrwTree.TreeNode tn3A   = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 1 with multiple columns. " +
                                                "Has two detail levels.", true, ThreeState.on,
                                                TArrowOrientation.Up,
                                                "Node 1."), null, false, informationSmallGraphic, true);
tn3A.Editable = true;
tree3.RootNodes.Add (tn3A);
XrwTree.TreeNode tn3A1  = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 1.1 with multiple columns. " +
                                                "Has one detail level.", false, ThreeState.off,
                                                TArrowOrientation.Down,
                                                "Node 1.1."), null, false, questionSmallGraphic, true);
tn3A1.Editable = true;
tn3A.Nodes.Add (tn3A1);
XrwTree.TreeNode tn3A11 = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 1.1.1 with multiple columns. " +
                                                "Has no detail level.", true, ThreeState.unset,
                                                TArrowOrientation.Left,
                                                "Node 1.1.1."), null, false, attentionSmallGraphic, true);
tn3A11.Editable = true;
tn3A1.Nodes.Add (tn3A11);
XrwTree.TreeNode tn3A2  = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 1.2 with multiple columns. " +
                                                "Has one detail level.", false, ThreeState.on,
                                                TArrowOrientation.Right,
                                                "Node 1.2."), null, false, questionSmallGraphic, true);
tn3A2.Editable = true;
tn3A.Nodes.Add (tn3A2);
XrwTree.TreeNode tn3A21 = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 1.2.1 with multiple columns. " +
                                                "Has no detail level.", true, ThreeState.off,
                                                TArrowOrientation.Up,
                                                "Node 1.2.1."), null, false, attentionSmallGraphic, true);
tn3A21.Editable = true;
tn3A2.Nodes.Add (tn3A21);
XrwTree.TreeNode tn3B   = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 2 with multiple columns. " +
                                                "Has one detail level.", false, ThreeState.unset,
                                                TArrowOrientation.Down,
                                                "Node 2."), null, false, informationSmallGraphic, true);
tn3B.Editable = true;
tree3.RootNodes.Add (tn3B);
XrwTree.TreeNode tn3B1  = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 2.1 with multiple columns. " +
                                                "Has no detail level.", true, ThreeState.on,
                                                TArrowOrientation.Left,
                                                "Node 2.1."), null, false, questionSmallGraphic, true);
tn3B1.Editable = true;
tn3B.Nodes.Add (tn3B1);
XrwTree.TreeNode tn3B2  = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 2.2 with multiple columns. " +
                                                "Has no detail level.", false, ThreeState.off,
                                                TArrowOrientation.Right,
                                                "Node 2.2."), null, false, questionSmallGraphic, true);
tn3B2.Editable = true;
tn3B.Nodes.Add (tn3B2);
XrwTree.TreeNode tn3C   = new XrwTree.TreeNode (new GridViewNodeData ("Tree-node 3 with multiple columns. " +
                                                "Has one detail level.", true, ThreeState.unset,
                                                TArrowOrientation.Up,
                                                "Node 3."), null, false, informationSmallGraphic, true);
tn3C.Editable = true;
tree3.RootNodes.Add (tn3C);
...

The next sample code shows how to adopt the default column (Columns[0]) and to define further columns suitable for the data types used by the tree. The column data binding is done according to GridViewNodeData's properties Name, Col2, Col3, Col4 and Col5.

C++
// Configure and define the tree columns.
tree3.Columns[0].DisplayMemberBinding = new XrwBinding ("Name");
tree3.Columns[0].Header = new XrwGridViewColumnHeader ("Name");
tree3.Columns[0].Editable = true;
XrwGridViewColumn col2 = new XrwGridViewColumn (XrwTree.MeasureCommonCell,
                                                XrwTree.MeasureCommonCellEditorOffset, XrwTree.DrawCommonCell);
col2.DisplayMemberBinding =  new XrwBinding ("Col2");
col2.Header = new XrwGridViewColumnHeader ("Boolean");
col2.Header.TextColor = tree3.TextColor;
col2.Editable = true;
tree3.Columns.Add (col2);
XrwGridViewColumn col3 = new XrwGridViewColumn (XrwTree.MeasureCommonCell,
                                                XrwTree.MeasureCommonCellEditorOffset, XrwTree.DrawCommonCell);
col3.DisplayMemberBinding =  new XrwBinding ("Col3");
col3.Header = new XrwGridViewColumnHeader ("ThreeState");
col3.Header.TextColor = tree3.TextColor;
col3.Editable = true;
tree3.Columns.Add (col3);
XrwGridViewColumn col4 = new XrwGridViewColumn (XrwTree.MeasureCommonCell,
                                                XrwTree.MeasureCommonCellEditorOffset, XrwTree.DrawCommonCell);
col4.DisplayMemberBinding =  new XrwBinding ("Col4");
col4.Header = new XrwGridViewColumnHeader ("Enum");
col4.Header.TextColor = tree3.TextColor;
col4.Editable = true;
tree3.Columns.Add (col4);
XrwGridViewColumn col5 = new XrwGridViewColumn (XrwTree.MeasureCommonCell,
                                                XrwTree.MeasureCommonCellEditorOffset, XrwTree.DrawCommonCell);
col5.DisplayMemberBinding =  new XrwBinding ("Col5");
col5.Header = new XrwGridViewColumnHeader ("Text");
col5.Header.TextColor = tree3.TextColor;
col5.Editable = true;
tree3.Columns.Add (col5);

A column creation always registers three delegates,

  • one to calculate a cell's required size,
  • one to calculate a cell's editor offset and
  • one to draw the cell's content.

The default tree column (Columns[0]) is always created registering the XrwTree.MeasureCommonCell(), the XrwTree.MeasureCommonCellEditorOffset() and the XrwTree.DrawCommonCell() default delegate implementations to the MeasureCellMeasureCellEditorOffset and DrawCell handler. Because the columns 2 to 5 use data types, that have built-in support (boolean, tree state, enum, and text) for cell measurement, editor offset measurement and drawing, all of them register the XrwTree.MeasureCommonCell(), the XrwTree.MeasureCommonCellEditorOffset() and the XrwTree.DrawCommonCell() default delegate implementations as well.

If there are inidividual data types used for the data object's properties, bound to columns, it might be required to register individual delegats to do cell measurement, editor offset measurement and drawing.

The next sample code shows how to manipulate the viewport and tree behaviour. The delegates are connected to three radio buttons of one radio group and one toggle button. They realize the switch between disabled column dragging, header animated column dragging and full animated header dragging as well as between displayed expander and hidden expander.

C++
// Viewport and tree manipulation.
radioTree3DragNever.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportTree3.Header != null)
        viewportTree3.Header.ColumnDrag = ColumnDragType.Never;
};
radioTree3DragHeaderUpdate.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportTree3.Header != null)
        viewportTree3.Header.ColumnDrag = ColumnDragType.HeaderAninated;
};
radioTree3DragFullUpdate.SwitchedOn += delegate(XrwRectObj source)
{
    if (viewportTree3.Header != null)
        viewportTree3.Header.ColumnDrag = ColumnDragType.FullAnimated;
};
toggleTree3ShowExpander.SwitchedOn += delegate(XrwRectObj source)
{
    tree3.ShowExpander = true;
    tree3.InvokeRedraw ();
};
toggleTree3ShowExpander.SwitchedOff += delegate(XrwRectObj source)
{
    tree3.ShowExpander = false;
    tree3.InvokeRedraw ();
};

This is what the most advanved multi column tree looks like:

Image 15

The column resizing and column reordering works exactly as descibed for the multi column list sample.

Click behaviour, command invocation and in-place editing

The XrwList widget

  • selects a list item on a single click,
  • provides multi selection for ranges (with [Shift] key pressed) or noncoherent items (with [Ctrl] key pressed),
  • provides the possibility to invoke a delegate on a list item's double click and
  • provides the possibility to invoke in-place editing on a list items's second click.

The XrwTree widget

  • selects a tree none on a single click,
  • collapses/expands a tree node on double click or on single click at a tree node's expander,
  • provides the possibility to invoke a delegate on a tree node's double click and,
  • provides the possibility to invoke in-place editing on a tree node's second click.

For XrwList and XrwTree widget the double click event must have a delegate registered to XrwList.ItemCommandInvoke or XrwTree.NodeCommandInvoke. This feature is rarely used and not demonstrated by the sample application.

The multi selection capabilities are demonstrated by

  • the more advanved XrwList examples showing the large icon view,
  • small icon view and
  • list view as well as by
  • the XrwList multi column list view sample 'Advanved list test'.

For multi selection the MultiSelect property must be set to true .

The in-place editing capabilities are demonstrated by

  • the XrwList multi column list view sample 'Advanved list test' and
  • the XrwTree multi column tree view sample 'Advanced tree test'.

For in-place editing, the XrwList.Editable or XrwTree.Editable property must be enabled explicitly for every column and every list item/tree node because it is set false by default.

The demand to explicitly enable editing for every column and every list item/tree node offers a very accurate control.

The next sample code shows how to achive this for XrwList (for XrwTree the calls are identical):

C++
// Enable editing for distinct items.
...
ln1A.Editable = true;
...
ln1B.Editable = true;
...

// Enable editing for distict columns.
...
list1.Columns[0].Editable = true;
...
col2.Editable = true;
...

In-place editing is currently built-in supported for columns, that display

  • single line text (System.String),
  • boolean (System.Boolean),
  • three-state (System.ThreeState) and
  • enumerations (System.Enum).

The in-place editing is triggered by a second click on an already selected list item/tree node within the column to edit.

The XrwList.HandleButtonReleaseDefault preregistered ButtonRelease event's delegate call XrwList.HandleEditRequest subsequently to invoke in-place editing for the selected list item if the prerequisites are met.

The XrwTree.HandleButtonReleaseDefault preregistered ButtonRelease event's delegate call XrwTree.HandleEditRequest subsequently to invoke in-place editing for the selected tree node if the prerequisites are met.

Both HandleEditRequest methods calculate the column to edit and for that column they either

  • switch the value (for System.Boolean or System.ThreeState types) or
  • calculate the editor area (position and size) and call the cell editor.

Currently the determination of the required cell editor is done by XrwBaseCellEditorShell.FindCellEditor. This static method creates a specialized cell editor shell, suitable for the data type (System.String or System.Enum) of the column to edit.

Points of Interest

I wrote the sample application to create an almost complete interactive test for XrwList and XrwTree widgets and to demonstrate/explain the advanced features in cooperation with the XrwViewport and XrwViewportGridViewHeader widgets.

History

This is the initial version of the article fom 13. August 2014.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Celonis SA
Germany Germany
I am currently the CEO of Symbioworld GmbH and as such responsible for personnel management, information security, data protection and certifications. Furthermore, as a senior programmer, I am responsible for the automatic layout engine, the simulation (Activity Based Costing), the automatic creation of Word/RTF reports and the data transformation in complex migration projects.

The main focus of my work as a programmer is the development of Microsoft Azure Services using C# and Visual Studio.

Privately, I am interested in C++ and Linux in addition to C#. I like the approach of open source software and like to support OSS with own contributions.

Comments and Discussions

 
-- There are no messages in this forum --