Skip to content

Tree List Control Programming Discussion

Gary edited this page Mar 10, 2015 · 4 revisions

Table of Contents

The ATF Tree List Control Sample demonstrates the usage of the TreeListControl and DataEditor classes to display and edit hierarchical data in a tree view with details in columns.

Programming Overview

This sample application does not use MEF or the ATF DOM, and it has no menus or tool buttons. The UI simply contains one Form object.

The Form constructor loads hierarchical data from an XML file (CoolSUVs.xml) and displays it in a TreeListControl. A TreeControlAdapter is used to adapt the TreeListControl to TreeView, which provides a tree view of the XML document. It also creates four columns (TreeListView.Column objects) to display data details in the TreeListControl. It sets a TreeListItemRenderer, which decides how to draw columns in the TreeListControl. The Form also provides a handler for tree data change events, which updates the data source after users edit values in the UI.

The sample contains a TreeView class that implements the ITreeView and IItemView interfaces to display items in a tree of XML data. TreeView maps tree node details to various DataEditor objects based on the type of values for each detail (column). These DataEditors are responsible for display and editing the values in the TreeListControl.

TreeListControl Initialization

The Main() function is very simple:

static void Main()
{
	Application.EnableVisualStyles();
	Application.SetCompatibleTextRenderingDefault(false);
  
	var mainform = new Form1();
	var automation = new  AutomationSetup(mainform);
	Application.Run(mainform);
}   

This is standard code to start a WinForms application. The AutomationSetup() call is for internal ATF automated testing, so applications do not need this.

LoadHierarchicalXmlData Function

The Form1 constructor's main task is to call LoadHierarchicalXmlData() to load hierarchical data from an XML file and display it in a TreeListControl.

LoadHierarchicalXmlData() accomplishes this by creating and configuring the TreeListControl, which derives from an ATF TreeControl to display and edit hierarchical data in a tree view with details in columns:

void LoadHierarchicalXmlData()
{

	var treeListControl = new TreeListControl();

	treeListControl.Dock = DockStyle.Fill;
	treeListControl.ShowRoot = false;
	treeListControl.LabelEditMode = TreeControl.LabelEditModes.EditOnF2 | TreeControl.LabelEditModes.EditOnClick;
	treeListControl.NodeDataEdited += treeListControl_NodeDataEdited;
	Controls.Add(treeListControl);
	...

The function sets the Dock and ShowRoot properties to specify docking behavior and to not show the root. Next, it sets LabelEditMode to allow a tree label to be edited by either pressing F2 or clicking the tree node. It subscribes to the NodeDataEdited event to process node data changes; for details on this event handling, see Node Data Change Handling. Finally, it adds the TreeListControl to the list of controls in the Form.

The next lines in LoadHierarchicalXmlData() get the path of the XML file with the hierarchical SUV data and create a TreeView:

Assembly assembly = Assembly.GetExecutingAssembly();
string startupPath = Path.GetDirectoryName(new Uri(assembly.GetName().CodeBase).LocalPath);
var xmlPath = Path.Combine(startupPath, "CoolSUVs.xml");

var treeView = new TreeView(xmlPath, new DataEditorTheme(treeListControl.Font));

TreeView is a TreeListControl class that provides a tree view of an XML document. In other words, TreeView is the data adapter that maps the XML node tree to the tree control. For more information, see TreeView Class.

LoadHierarchicalXmlData() finishes by creating other tree related objects needed:

	var treeControlAdapter = new TreeControlAdapter(treeListControl);
	treeControlAdapter.TreeView = treeView;

	treeListControl.ItemRenderer = new TreeListItemRenderer(treeView);

	treeListControl.Columns.Add(new TreeListView.Column("MPG",  80));
	treeListControl.Columns.Add(new TreeListView.Column("Weight", 80));
	treeListControl.Columns.Add(new TreeListView.Column("Color", 80));
	treeListControl.Columns.Add(new TreeListView.Column("MSRP", 80));
	treeListControl.ExpandAll();
}

TreeControlAdapter is a class to adapt the TreeListControl to a data context that implements ITreeView, which is a TreeView in this sample. TreeListControl uses TreeControlAdapter to populate the hierarchical data in the TreeListControl, and to display the MSRP for a SUV on the right side of the tree control. Note TreeControlAdapter uses TreeView to adapt the data view. Both slider and textbox controls are used to display and edit the MSRP.

The method creates a TreeListItemRenderer, which decides how to draw columns in a TreeListControl, and sets the TreeListControl.ItemRenderer property to this TreeListItemRenderer.

Finally, LoadHierarchicalXmlData() constructs a TreeListView.Column for each node detail desired and adds them to the TreeListControl's Columns collection.

Node Data Change Handling

When the user changes tree node data with a textbox or slider, you need to handle the data edited event to propagate the change back to the application. Here is the node data change event handler in the Form1 class:

void treeListControl_NodeDataEdited(object sender, TreeListControl.NodeEditEventArgs e)
{
	PrintData(e);
	var editedElement = e.Node.Tag as XElement;
	if (e.EditedData.Name == "MSRP")
	{
		editedElement.Attribute("msrp").Value = e.EditedData.ToString();
	}
	else if (e.EditedData.Name == "Weight")
	{
		editedElement.Attribute("weight").Value = e.EditedData.ToString();
	}
	else if (e.EditedData.Name == "Color")
	{
		editedElement.Attribute("color").Value = e.EditedData.ToString();
	}
	e.Node.TreeControl.Invalidate();         
}

This handler obtains the edited element as a LINQ XML element from the TreeListControl node's Tag property. TreeListControl.NodeEditEventArgs.EditedData contains the current value of the node from user editing. Based on the Name of EditedData, it updates the appropriate Attribute Value with the newly edited value. For example, this handler updates the MSRP value matched by column name, and the XML "suv" element's "msrp" attribute is overwritten with the current value.

Finally, the handler invalidates the TreeControl so it will be refreshed and display the new value.

XML Data

CoolSUVs.xml contains SUV data:

<?xml version="1.0" encoding="utf-8" ?>
<cars>
  <America>
    <suv name="Devestator" manufacturer="Crysis Group LLC" min ="20295" max ="32795" msrp="20295" mpg="26/19" weight="3818" color="RoyalBlue"/>
    <suv name="Craterer" manufacturer="General Meteors" min ="42362" max ="63335" msrp="45550" mpg="23/16" weight="5308" color="-55296"/>
  </America>

  <Asia>
    <suv name="Outandback" manufacturer="Boomerang Heavy Industries" min ="23407" max ="32995" msrp="24895" mpg="33/25" weight="3593" color="-12560509"/>
    <suv name="Carbonizer" manufacturer="Atoma Motors" min ="22142" max ="29850" msrp="26350" mpg="31/24" weight="3435" color="-7879179"/>
  </Asia>

  <Europe>
    <suv name="Impactor" manufacturer="Mannwagen" min ="24955" max ="39235" msrp="39235" mpg="26/21" weight="3404" color="-12800"/>
    <suv name="Engorger" manufacturer="Gris Cars" min ="37318" max ="47400" msrp="39700" mpg="25/16" weight="4394" color="-47872"/>
  </Europe>
</cars>

The XML has this element hierarchy:

  • "cars"
    • "Location" ("America", "Asia", or "Europe")
      • "suv"
The "suv" element contains attributes defining SUV information.

TreeView Class

This sample application provides the TreeView class that implements interfaces to display items in a tree of XML data:

public class TreeView:  ITreeView, IItemView

Form1.LoadHierarchicalXmlData() constructed the TreeView this way:

var treeView = new TreeView(xmlPath, new DataEditorTheme(treeListControl.Font));

The XML file path was created for CoolSUVs.xml. DataEditorTheme is a data editing theme, which determines how elements in data editors are rendered. Its constructor provided the font and sets default theme properties, which could also be set through these properties, such as TextBrush.

Here's the constructor:

public TreeView(string xmlFilePath, DataEditorTheme theme)
	{
		m_xmlDoc = XDocument.Load(xmlFilePath);// loads the hierarchical data using Linq to XML API
		m_dataEditorTheme = theme;
	}

The constructor loads the hierarchical data from the file CoolSUVs.xml into an System.Xml.Linq.XDocument object using the LINQ to XML API in System.Xml.Linq.

TreeView ITreeView Implementation

The ITreeView interface simply provides access to the tree nodes. Root gets the tree root from the XDocument.Root property:

public object Root
{
	get { return m_xmlDoc.Root; }
}

GetChildren() gets child nodes by casting the parent node to a System.Xml.Linq.XElement:

public IEnumerable<object> GetChildren(object parent)
{
	var node = parent as XElement;
	if (node == null)
		return Enumerable.Empty<object>();
	return node.Elements();
}

TreeView IItemView Implementation

In an ATF-based application, node data is normally obtained by calling IItemView.GetInfo(), which has an ItemInfo parameter. The ItemInfo class contains numerous properties to access information about an item displayed in a tree node (or list). The implementation of GetInfo() in TreeView basically fills out properties in the ItemInfo from the supplied node so they are ultimately displayed in the tree:

public void GetInfo(object item, ItemInfo info)
{
	var node = item as XElement;
	if (node == null)
		return;
	info.IsLeaf = !node.HasElements;
	if (node.Name.LocalName == "suv")
	{
		var attribute = node.Attribute("name");
		if (attribute != null)
		{
			info.Label = (string) attribute;

			var mpg = new StringDataEditor(m_dataEditorTheme)
			{
				Owner = item,
				Value = (string)node.Attribute("mpg"),
				ReadOnly =  true,
				Name = "MPG", // (column)name of the data value, should be unique among columns,
			};
		...
		}
	}
	else
		info.Label = node.Name.LocalName;
}

First, the tree item is cast to an XElement node (because XElement objects are returned for nodes in the ITreeView implementation). ItemInfo.IsLeaf is readily set as the complement of the node's XElement.HasElements property.

Next, it checks the node Name.LocalName, which corresponds to the element name in the XML. If it's not "suv" and hence one of the ancestor nodes in the tree, ItemInfo.Label is set to the local name.

Handling the "suv" element name is more involved, because data must be extracted from the element's attributes. This code gets the attribute name (var attribute = node.Attribute("name");) and then tests to make sure it's not null. If not, it casts the name attribute to a string for the ItemInfo.Label value.

For each type of attribute, a DataEditor of the appropriate type is created. In the code above, a StringDataEditor is constructed for the "mpg" attribute. The StringDataEditor's properties are then set in the following block. For example, ReadOnly is set true, so that item can't be changed in the displayed tree.

Color attributes require special handling:

var color = new ColorDataEditor(m_dataEditorTheme)
{
	Owner = item,
	Name = "Color",
};
color.Parse((string) node.Attribute("color"));

Because color can be represented as a standard color name (like "RoyalBlue") or a number (like "-55296"), the ColorDataEditor.Parse() method is needed to parse the given string representation and set the color data value.

After all the DataEditor objects are created, they are placed in an array that becomes the value of ItemInfo.Properties:

info.Properties = new object[] { mpg, weight, color, msrp };

The TreeListItemRenderer.DrawData() method extracts the node details from ItemInfo.Properties, and then displays these details on the right side of the tree control.

DataEditor Classes

DataEditor is an abstract base class that can provide a user interface (UI) for representing and editing values of objects of supported data types.

TreeListControl uses several DataEditor-derived objects to hold, display, and edit data values of a tree item. Currently ATF provides only three DataEditors: StringDataEditor, FloatDataEditor, and ColorDataEditor.

It should be straightforward to add your own data editors by subclassing DataEditor for different data types or a customized UI. To implement a custom data editor, you must perform the following tasks at least:

  • Define a class that derives from DataEditor.
  • Override the Measure() method to inform the parent control how much screen space it would like to have.
  • Override the PaintValue() method to implement the display of the value's representation.
More data editors may be added in future releases.

Topics in this section

Clone this wiki locally