Skip to content

Tree List Editor Programming Discussion

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

The ATF Tree List Editor Sample demonstrates how to use the ATF tree controls TreeListView and its enhancement, TreeListViewEditor. TreeListView uses TreeListViewAdapter, which adapts TreeListView to display data. TreeListEditor shows all styles of a TreeListView as well as an unadorned TreeListView, implementing them all as components.

TreeListEditor also demonstrates how to create data and display its properties in a PropertyEditor component.

Programming Overview

The CommonEditor component is the base class for most of the tree editors in TreeListEditor. It shows how to use the TreeListViewEditor class, which uses a TreeListView with a TreeListViewAdapter. CommonEditor creates a UserControl to hold the TreeListViewEditor and related tool buttons and provides the control host client for the UserControl. The tool buttons generate tree data and perform other functions that are handled by CommonEditor's methods.

The RawTreeListView component displays a folder's file hierarchy and operates somewhat like the CommonEditor, but uses a TreeListView.

TreeListEditor creates a data item class DataItem derived from CustomTypeDescriptor to display data in the trees. This also allows displaying data in a PropertyEditor component. The DataContainer class can contain a collection of DataItem and implements ITreeListView and other interfaces to allow viewing data in a tree. DataContainer's methods also generate tree data.

The TreeListViewAdapter wraps a TreeListView and allows supplying data to the TreeListView through its View property, which implements the ITreeListView interface. This View property contains a DataContainer object, which supplies what the TreeListViewAdapter needs to display data.

TreeListEditor Components

TreeListEditor follows the standard WinForms initialization pattern discussed in WinForms Application. In addition to standard ATF components, it adds the following components of its own to the MEF TypeCatalog:

  • List: Add the standard list view editor, based on the CommonEditor component. For details on this and the other editor components (except RawTreeListView), see CommonEditor Component.
  • CheckedList: Add the checked list view editor, based on the CommonEditor component.
  • VirtualList: Add the virtual list view editor, based on the CommonEditor component.
  • TreeList: Add the tree list view editor, based on the CommonEditor component.
  • CheckedTreeList: Add the checked tree list view editor, based on the CommonEditor component.
  • RawTreeListView: Add a tree list editor for displaying a hierarchical file system. For details, see RawTreeListView Component.

CommonEditor Component

The List, CheckedList, VirtualList, TreeList, and CheckedTreeList components all derive from the CommonEditor component, and these components all reside in the file Editor.cs.

CommonEditor is the common editor used for all the tree view editors. The other components' implementation is trivial, because all needed capability is provided in CommonEditor.

CommonEditor derives from TreeListViewEditor:

class CommonEditor : TreeListViewEditor, IInitializable, IControlHostClient

TreeListViewEditor adds extra functions to a TreeListView, including a TreeListViewAdapter and a right-click context edit menu. The TreeListView class provides a tree with a list view. TreeListViewAdapter wraps a TreeListView and allows a user to supply data to it through the ITreeListView interface. For more information on the operation of TreeListViewAdapter with this sample, see TreeListViewAdapter Class.

CommonEditor Constructor

CommonEditor's constructor specifies a display name, tree list style, and flags indicating which buttons to display:

public CommonEditor(
    string name,
    TreeListView.Style style,
    Buttons flags,
    IContextRegistry contextRegistry,
    ISettingsService settingsService,
    IControlHostService controlHostService)

The constructor creates a UserControl control to hold the TreeListViewEditor plus any buttons desired.

The name parameter specifies text to display in the user interface for the tree, which is on the window for the tree.

The TreeListView.Style style parameter is an enumeration specifying the tree style:

  • List: A list.
  • CheckedList: A list with check boxes next to items.
  • VirtualList: A list supporting thousands of items, instantiated as needed.
  • TreeList: A tree with columns (default).
  • CheckedTreeList: A tree list with check-boxes next to the items.
TreeListEditor demonstrates all these styles in its five editor windows.

The flags parameter tells CommonEditor which buttons to add to the tree control. The parameter is a flag bit mask, so it can specify any combination of buttons. Each button is created with the CreateButton() method, which creates a new Button with the desired text, sizing it to fit the text, and positioning it on the UserControl. All the buttons have the same Click event handler BtnClick(), but have their Tag property set to the Buttons enumeration value for that button. The constructor invokes RegisterControl() with the Control Host Registry for the UserControl.

BtnClick Method

The BtnClick() event handler uses Button.Tag to determine what function to perform. Some of these functions generate data and place it in the tree. For a discussion of data generation, see Tree Data Generation.

CommonEditor Control Host Client

CommonEditor implements IControlHostClient very simply for the UserControl. Its Activate() method just sets the active context to the TreeListViewEditor.View property, which holds the ITreeListView associated with data displayed in the control. For more information on data handling in the tree, see Editors Derived from CommonEditor and Tree Data Generation.

Editors Derived from CommonEditor

All the other tree editors derive from CommonEditor. These other components do very little. Here's the entire CheckedList definition, for example:

[ImportingConstructor]
public CheckedList(
    IContextRegistry contextRegistry,
    ISettingsService settingsService,
    IControlHostService controlHostService)
    : base(
        "Checked List",
        TreeListView.Style.CheckedList,
        Buttons.AddFlat,
        contextRegistry,
        settingsService,
        controlHostService)
{
    TreeListView.NodeSorter =
        new DataComparer(TreeListView);

    View = new DataContainer();

    TreeListView.NodeChecked += TreeListViewNodeChecked;
}

The constructor calls the base constructor to specify the editor name, to indicate it's a CheckedList, and to specify that only the "Add Flat" button appears with this editor.

The TreeListView.NodeSorter property specifies the way nodes are sorted in the TreeListView. The TreeListView property is in the TreeListViewEditor and contains its TreeListView. For information on the sorting class, see DataComparer Class.

The View property is also in the TreeListViewEditor and is a ITreeListView indicating the data view for the TreeListView. The DataContainer class implements ITreeListView and represents a set of data generated and placed in the tree. For details, see Tree Data Generation.

This constructor also specifies the handler for the TreeListView.NodeChecked event:

private void TreeListViewNodeChecked(object sender, TreeListView.NodeCheckedEventArgs e)
{
    Outputs.WriteLine(
        OutputMessageType.Info,
        "{0} {1}",
        e.Node.Label,
        e.Node.CheckState == CheckState.Checked
            ? "checked"
            : "unchecked");
}

This method writes a message to the Output window in TreeListEditor. This window is created and handled by the Outputs component, which provides static methods to access its Output window. TreeListEditor imports Outputs.

RawTreeListView Component

RawTreeListView creates the Raw TreeListView Usage window in TreeListEditor to display a folder's contents in a tree. In this respect, it has similarities to the ATF File Explorer Sample. That sample's FolderViewer component displays the entire file hierarchy of the "C:" drive's contents as a tree. For details, see FolderViewer Component.

RawTreeListView also operates somewhat like the CommonEditor component. It creates a UserControl to host a tree control and buttons. Its CreateButton() method is identical to CommonEditor's. It also implements IControlHostClient to serve as the UserControl control host client, which functions similarly to CommonEditor's client. However, RawTreeListView uses a TreeListView for the tree control rather than a TreeListViewEditor.

RawTreeListView Constructor

The constructor creates all the buttons needed, adds them to the UserControl, and subscribes to events. The event handlers are all in RawTreeListView. The TreeListView is also created and added to the UserControl. Finally, the constructor invokes RegisterControl() with the Control Host Registry to register UserControl.

IInitializable.Initialize Method

RawTreeListView also implements IInitializable.Initialize(), which does nothing. However, implementing IInitializable forces this component to be instantiated, which would not happen otherwise, because nothing imports it. For more details on MEF initialization, see Initializing Components.

MySorter Class

MySorter is the sorter that RawTreeListView provides for the tree, as seen in this code from RawTreeListView's constructor.

m_control = new TreeListView { Name = NameText, AllowDrop = true };
m_control.NodeSorter = new MySorter(m_control);

MySorter is somewhat similar to the DataComparer class used by CommonEditor, but sorts folder and file names rather than generated data. For information on how DataComparer works, see DataComparer Class.

Other Methods

Most of the methods and code in RawTreeListView either handle events or enumerate the files in a folder to display them. The folder handling uses standard .NET classes, such as DirectoryInfo and Directory.

Tree Data Generation

The file DataGenerator.cs contains several classes used in generating display data for the trees, in particular DataItem and DataContainer.

DataContainer in particular works with the TreeListViewAdapter class to display generated data. To learn how this works, see TreeListViewAdapter Class.

DataItem Class

DataItem represents a set of generated data displayed in a tree; the PropertyEditor component can also display certain DataItem properties. DataItem derives from System.ComponentModel.CustomTypeDescriptor, which is a simple default implementation of the System.ComponentModel.ICustomTypeDescriptor interface:

class DataItem : CustomTypeDescriptor

ICustomTypeDescriptor supplies dynamic custom type information for an object and is useful for describing data items in an application.

PropertyEditing Attribute

DataItem has a set of properties, such as Parent and Children, that allow it to represent hierarchical data. Consider these two properties:

/// <summary>
/// Gets whether item has children</summary>
[PropertyEditingAttribute]
public bool HasChildren
{
    get { return m_children.Count > 0; }
}

/// <summary>
/// Gets item's children</summary>
public ICollection<DataItem> Children
{
    get { return m_children; }
}

The Children property gets an ICollection<DataItem> of all the child data.

Note that the property HasChildren is marked with the attribute [PropertyEditingAttribute], but Children isn't.

[PropertyEditingAttribute] is a custom attribute that TreeListEditor uses to mark the properties to be displayed in the Property Editor. It's defined this way:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class PropertyEditingAttribute : Attribute
{
}

The PropertyEditingAttribute class itself is marked with the attribute [AttributeUsageAttribute()], which tells how the defined attribute can be used. In this case, the AttributeTargets.Property enumeration value means the attribute applies to C# properties. AllowMultiple = true means that the attribute can be used more than once in a class. The attribute [PropertyEditingAttribute] has no parameters, so the class is very simple, requiring nothing but its declaration. The properties here are marked with the full attribute name [PropertyEditingAttribute], although [PropertyEditing] also works, by convention.

PropertyPropertyDescriptor Class

DataItem defines a property descriptor class PropertyPropertyDescriptor for C# properties. Note that the word "property" is used in two senses here: for a C# property, as well as for a property represented by a property descriptor.

public class PropertyPropertyDescriptor : PropertyDescriptor

PropertyPropertyDescriptor derives from System.ComponentModel.PropertyDescriptor.

GetProperties Method

When a selection of tree items occurs, through a chain of events, a collection of property descriptors is requested by calling the GetProperties() method for each selected item that implements ICustomTypeDescriptor, which is DataItem in TreeListEditor. This eventually results in the properties for the last selected item being displayed in the Property Editor window by the PropertyEditor component.

The class deriving from CustomTypeDescriptor, DataItem, implements the GetProperties() method:

public override PropertyDescriptorCollection GetProperties()
{
    var props = new PropertyDescriptorCollection(null);

    foreach (var property in GetType().GetProperties())
    {
        var propertyDesc =
            new PropertyPropertyDescriptor(property, GetType());

        propertyDesc.ItemChanged += PropertyDescItemChanged;

        foreach (Attribute attr in propertyDesc.Attributes)
        {
            if (attr.GetType().Equals(typeof(PropertyEditingAttribute)))
                props.Add(propertyDesc);
        }
    }

    return props;
}

This method iterates through all the C# properties defined in the DataItem class, obtained from GetProperties(), and creates a PropertyPropertyDescriptor for each property. It then checks all the Attributes for the data in the property descriptor. If any of the attributes is PropertyEditingAttribute, it adds the property descriptor to the PropertyDescriptorCollection object props. It does not add the property descriptor more than once, because the only attribute marking properties in DataItem is [PropertyEditingAttribute].

This GetProperties() method includes properties that have the checked for attribute. This guarantees that only [PropertyEditingAttribute] marked properties are listed in the Property Editor.

DataContainer Class

This class provides the ability to display data in tree controls, and it also generates the data as DataItem objects. It contains data in its m_data field, which is of type List<DataItem>.

DataContainer Interfaces

DataContainer implements these interfaces, each of which are discussed below:

class DataContainer : ITreeListView, IItemView, IObservableContext, ISelectionContext, IValidationContext
ITreeListView Interface

ITreeListView defines a view on hierarchical data and consists of the following:

  • IEnumerable<object> Roots: Get the root level objects of the tree view.
  • IEnumerable<object> GetChildren(object parent): Get an enumeration of the children of the given parent.
  • string\[[\]] ColumnNames: Get the list column's names.
This is very similar to the ITreeView interface, except that ITreeListView allows more than one root object and also adds ColumnNames. The implementation here uses the m_data field along with DataItem's properties HasChildren and Children.
IItemView Interface

This interface's GetInfo() method gets a given object's ItemInfo. The ItemInfo class contains numerous properties to access information about an item displayed in a tree node (or list). The implementation of GetInfo() in DataContainer just fills out a few properties:

public void GetInfo(object obj, ItemInfo info)
{
    var data = obj.As<DataItem>();
    if (data == null)
        return;

    info.Label = data.Name;
    info.Properties =
        new object[]
        {
            data.Type.ToString(),
            data.Value
        };

    info.IsLeaf = !data.HasChildren;
    info.ImageIndex =
        data.HasChildren
            ? s_folderImageIndex
            : s_dataImageIndex;
}

First, this method adapts the item to a DataItem. After copying the Name, it creates an array of objects for the Properties property, containing a string of the item's type and its value. It sets IsLeaf, and then sets up ImageIndex to display a folder image for an item with children.

IObservableContext Interface

IObservableContext contains events for items being changed, added, or removed. This is required so the tree display can be refreshed after items change. These events are raised when data is modified.

When the TreeListViewAdapter.View property is set, TreeListViewAdapter adapts the value of View to IObservableContext and subscribes the adapted value to these events. These events are thus handled by methods in TreeListViewAdapter. Also recall that View contains a DataContainer object. For details, see TreeListViewAdapter View Property and IObservableContext Events in particular. TreeListViewAdapter does the same thing for the IValidationContext interface.

ISelectionContext Interface

ISelectionContext is the interface for selection contexts, which have selectable items. Here, the selectable objects are DataItem objects, as seen in this section of the ModifySelected() method, which is called when the "Modify Selected" button is clicked:

private void ModifySelected(IEnumerable<object> selection)
{
    Beginning.Raise(this, EventArgs.Empty);

    foreach (var obj in selection)
    {
        var data = obj.As<DataItem>();
        ...

The DataContainer class creates a Selection object, which is a collection representing a selection. It's easy to implement ISelectionContext using the methods available on a Selection object. The Selection property gets and sets the current selection, and is implemented by simply getting and setting the m_selection field, which contains the Selection object:

public IEnumerable<object> Selection
{
    get { return m_selection; }
    set { m_selection.SetRange(value); }
}

The other properties and methods in this interface get information about the selection, such as the count of selected objects and the last selected object. These methods are also implemented using m_selection, as in the LastSelected property:

public object LastSelected
{
    get { return m_selection.LastSelected; }
}

Another important thing in this interface are selection related events:

/// <summary>
/// Event that is raised before the selection changes</summary>
public event EventHandler SelectionChanging;

/// <summary>
/// Event that is raised after the selection changes</summary>
public event EventHandler SelectionChanged;

In its constructor, DataContainer subscribes to the class's Selection<T> selection change events:

m_selection.Changing += TheSelectionChanging;
m_selection.Changed += TheSelectionChanged;

TheSelectionChanging() simply raises an event:

private void TheSelectionChanging(object sender, EventArgs e)
{
    SelectionChanging.Raise(this, EventArgs.Empty);
}

Raising such selection events hooks into the event handling system that ultimately generates lists of property descriptors that property editors can use to display the common properties of selected items.

IValidationContext Interface

This interface provides events that validators can subscribe to and perform their validations at appropriate times:

  • Beginning: Raised before validation begins.
  • Cancelled: Raised after validation is cancelled.
  • Ending: Raised before validation ends.
  • Ended: Raised after validation ends.
Some of the data generation and modification methods raise these events.

When the TreeListViewAdapter.View property is set, TreeListViewAdapter adapts the value of View to IValidationContext and subscribes the adapted value to these events. These events are thus handled by methods in TreeListViewAdapter. For details, see TreeListViewAdapter View Property and IValidationContext Events in particular. TreeListViewAdapter does the same thing for the IObservableContext interface.

DataContainer Data Generation Methods

The event handler method BtnClick() handles clicking buttons on the tree windows, as described in BtnClick Method. BtnClick() calls DataContainer's data generation methods for some of the buttons, as seen in this section of the method:

private void BtnClick(object sender, EventArgs e)
{
    var btn = (Button)sender;
    var flags = (Buttons)btn.Tag;

    switch (flags)
    {
        case Buttons.AddFlat:
            DataContainer.GenerateFlat(
                View,
                (TreeListView.TheStyle != TreeListView.Style.TreeList) &&
                (TreeListView.TheStyle != TreeListView.Style.CheckedTreeList)
                    ? null
                    : TreeListViewAdapter.LastHit);
            break;

        case Buttons.AddHierarchical:
            DataContainer.GenerateHierarchical(
                View,
                TreeListViewAdapter.LastHit);
            break;
        ...

The GenerateFlat() method, for instance, has a first parameter of View, which is a property of TreeListViewEditor, from which the CommonEditor class derives. However, the View property in TreeListViewEditor simply gets and sets the View property in TreeListViewAdapter. The TreeListViewAdapter's View property holds an ITreeListView, that is, an object implementing ITreeListView. For TreeListEditor, this is a DataContainer object, because each of the tree editors makes an assignment like this:

View = new DataContainer();

For details, see Editors Derived from CommonEditor. For details on the View property, see TreeListViewAdapter View Property.

The second parameter of AddHierarchical() is either null (depending on the tree style) or the last data item selected.

Here's the GenerateFlat() method:

public static void GenerateFlat(ITreeListView view, object lastHit)
{
    var container = view.As<DataContainer>();
    if (container == null)
        return;

    container.GenerateFlat(lastHit.As<DataItem>());
}

The view parameter is adapted to DataContainer, which works because view is a DataContainer object. The private method GenerateFlat() is called, with a parameter of the last selected item, if any, which serves as the parent to which data objects are added:

private void GenerateFlat(DataItem parent)
{
    Beginning.Raise(this, EventArgs.Empty);

    var items = s_random.Next(5, 16);
    for (var i = 0; i < items; i++)
    {
        var data = CreateItem(parent);
        data.ItemChanged += DataItemChanged;

        if (parent != null)
            parent.Children.Add(data);
        else
            m_data.Add(data);

        ItemInserted.Raise(this, new ItemInsertedEventArgs<object>(-1, data, parent));
    }

    if (parent != null)
    {
        ItemChanged.Raise(this, new ItemChangedEventArgs<object>(parent));
    }

    Ending.Raise(this, EventArgs.Empty);
    Ended.Raise(this, EventArgs.Empty);
}

Notice that GenerateFlat() raises the IValidationContext events at the appropriate times: Beginning at the beginning, and then Ending and Ended at the end.

A System.Random object is used repeatedly in data generation to randomize the number of objects generated and the lengths of strings. For instance, the loop generates between 5 and 16 items, using the CreateItem() method to create DataItem objects.

The ItemChanged event is subscribed to for the new DataItem with the handler DataItemChanged. This handler raises the ItemChanged event of the IObservableContext interface. This is key to updating the tree with this new data. For details, see ItemChanged and ItemRemoved Events.

CreateItem() creates all the DataItem objects:

private static DataItem CreateItem(DataItem parent)
{
    var enumLength = Enum.GetNames(typeof(DataType)).Length;
    var name = CreateString(s_random.Next(2, 11));
    var type = (DataType)s_random.Next(0, enumLength);

    object value;
    switch (type)
    {
        case DataType.Integer:
            value = s_random.Next(0, 51);
            break;

        case DataType.String:
            value = CreateString(s_random.Next(5, 16));
            break;

        default:
            value = type.ToString();
            break;
    }

    var data =
        new DataItem(
            parent,
            name,
            type,
            value);

    return data;
}

This method generates a random object name, randomly chooses the type of the generated object, and then generates a random integer or string for the object's value. It constructs a new DataItem from this data and returns it.

DataComparer Class

DataComparer provides the functions to sort items in the tree lists. It is defined for a TreeListView.Node:

class DataComparer : IComparer<TreeListView.Node>

Its main function, Compare(), shows that TreeListView.Node.Tag is adapted to DataItem:

public int Compare(TreeListView.Node x, TreeListView.Node y)
{
    if ((x == null) && (y == null))
        return 0;

    if (x == null)
        return 1;

    if (y == null)
        return -1;

    if (ReferenceEquals(x, y))
        return 0;

    var lhs = x.Tag.As<DataItem>();
    var rhs = y.Tag.As<DataItem>();
    ...

The TreeListView.Node.Tag property gets or sets the user data attached to the node, which is a DataItem in this case. For details on how TreeListView.Node.Tag is set to a DataItem, see ItemChanged and ItemRemoved Events in the section TreeListViewAdapter Class.

TreeListViewAdapter Class

TreeListViewAdapter wraps a TreeListView and allows supplying data to the TreeListView through its View property, which implements the ITreeListView interface. The TreeListView.Node.Tag contains the data attached to the node, as already noted. This section shows how the TreeListViewAdapter makes that happen and how the View property is the key. TreeListViewAdapter also triggers updating the tree's display after data changes. Raising the appropriate events makes the updates occur.

This section also shows that by implementing ITreeListView, DataContainer allows displaying data in the tree with TreeListViewAdapter.

TreeListViewEditor View Property

As previously seen in Editors Derived from CommonEditor, the tree editors deriving from CommonEditor set the View property of TreeListViewEditor in their constructors:

View = new DataContainer();

Here's the definition of TreeListViewEditor.View

public ITreeListView View
{
    get { return m_treeListViewAdapter.View; }
    set { m_treeListViewAdapter.View = value; }
}

This shows that the TreeListViewEditor.View property actually comes from TreeListViewAdapter.View, because m_treeListViewAdapter holds the TreeListViewAdapter associated with the TreeListViewEditor. Therefore, the DataContainer object is the value that TreeListViewAdapter sees in View. Note that DataContainer implements ITreeListView, as required.

TreeListViewAdapter View Property

TreeListViewAdapter.View contains the actual DataContainer object, as just observed. Here is part of the implementation of TreeListViewAdapter.View:

public ITreeListView View
{
    get { return m_view; }
    set
    {
        if (m_view != value)
        {
            if (m_view != null)
            {
            ...
            m_view = value;

            if (m_view != null)
            {
                m_itemView = m_view.As<IItemView>();

                m_selectionContext = m_view.As<ISelectionContext>();
                if (m_selectionContext != null)
                {
                    m_selectionContext.SelectionChanging += SelectionContextSelectionChanging;
                    m_selectionContext.SelectionChanged += SelectionContextSelectionChanged;
                }
                else
                {
                    m_treeListView.NodeSelected += TreeListViewNodeSelected;
                }

                m_observableContext = m_view.As<IObservableContext>();
                if (m_observableContext != null)
                {
                    m_observableContext.ItemInserted += ObservableContextItemInserted;
                    m_observableContext.ItemChanged += ObservableContextItemChanged;
                    m_observableContext.ItemRemoved += ObservableContextItemRemoved;
                    m_observableContext.Reloaded += ObservableContextReloaded;
                }

                m_validationContext = m_view.As<IValidationContext>();
                if (m_validationContext != null)
                {
                    m_validationContext.Beginning += ValidationContextBeginning;
                    m_validationContext.Ending += ValidationContextEnding;
                    m_validationContext.Ended += ValidationContextEnded;
                    m_validationContext.Cancelled += ValidationContextCancelled;
                }
            }
        }

        Load();
    }
}

The property's setter attempts to adapt the new value of the View property to several context interfaces:

  • ISelectionContext
  • IObservableContext
  • IValidationContext
If successful, it subscribes the adapted DataContainer value to a set of events in the interface. Because DataContainer implements all these interfaces, DataContainer objects subscribe to all these events. However, all these event handlers are in TreeListViewAdapter. These handlers allow displaying data in the tree.

IObservableContext Events

These events relate to items in the context changing. They are raised by the data generation methods in DataContainer to update the display, as shown in DataContainer Data Generation Methods.

ItemInserted Event

Here's the handler for ItemInserted:

private void ObservableContextItemInserted(object sender, ItemInsertedEventArgs<object> e)
{
    if (m_treeListView.TheStyle != TreeListView.Style.VirtualList)
    {
        if (m_inTransaction)
            m_queueInserts.Add(e);
        else
            AddItem(e.Item, e.Parent);
    }
    else
    {
        if (e.Item is object[])
            VirtualListSize += ((object[])e.Item).Length;
        else
            VirtualListSize += 1;
    }
}

For non-virtual tree lists, the new item in e.Item is added to the queue m_queueInserts for later addition to the tree — or added immediately with the AddItem() method. The field m_inTransaction indicates whether a transaction is occurring or not, and is set in the IValidationContext events. For more details, see IValidationContext Events.

AddItem() creates a TreeListView.Node for the item with the CreateNodeForObject() method, which also calls UpdateNodeFromItemInfo(), discussed further in ItemChanged and ItemRemoved Events. AddItem() also adds nodes for children of the item, because this data can be hierarchical: a DataItem can have child DataItems.

ItemChanged and ItemRemoved Events

The event handlers for ItemChanged and ItemRemoved are similar to each other. Both handle virtual lists separately, and queue items that are to be changed or removed during a transaction — or perform the update immediately when there is no transaction. Here is ObservableContextItemChanged(), for instance:

private void ObservableContextItemChanged(object sender, ItemChangedEventArgs<object> e)
{
    if (m_treeListView.TheStyle != TreeListView.Style.VirtualList)
    {
        if (m_inTransaction)
            m_queueUpdates.Add(e);
        else
            UpdateItem(e.Item);
    }
    else
    {
        UpdateItem(e.Item);
    }
}

During a transaction, the item is added to m_queueUpdate; otherwise the tree is immediately updated with UpdateItem, which does the following:

private void UpdateItem(object item)
{
    TreeListView.Node node;
    if (!m_dictNodes.TryGetValue(item, out node))
        return;

    ItemInfo info =
        GetItemInfo(
            item,
            m_itemView,
            m_treeListView.ImageList,
            m_treeListView.StateImageList);

    UpdateNodeFromItemInfo(node, item, info);

    m_treeListView.Invalidate(node);
}

After getting item information from GetItemInfo(), UpdateItem() calls UpdateNodeFromItemInfo() with that information and then invalidates the tree view to force redrawing the tree.

Among other things, UpdateNodeFromItemInfo() makes the data stored in the tree easily accessible:

private static void UpdateNodeFromItemInfo(TreeListView.Node node, object item, ItemInfo info)
{
    node.Label = info.Label;
    node.Properties = info.Properties;
    node.ImageIndex = info.ImageIndex;
    node.StateImageIndex = info.StateImageIndex;
    node.CheckState = info.GetCheckState();
    node.Tag = item;
    node.IsLeaf = info.IsLeaf;
    node.Expanded = info.IsExpandedInView;
    node.FontStyle = info.FontStyle;
    node.HoverText = info.HoverText;
}

Note that node.Tag = item sets the TreeListView.Node.Tag property to the data item. For TreeListEditor, this is a DataItem object. The Compare() method takes advantage of this to access this data, as noted in DataComparer Class. In addition, the LastHit property gets the data for the last selected node from Tag:

public object LastHit
{
    get
    {
        object lastHit = m_treeListView.LastHit;
        return lastHit.Is<TreeListView.Node>() ? lastHit.As<TreeListView.Node>().Tag : this;
    }
}

This finally explains how the Tag property gets set to the data object, which is a DataItem instance.

Reloaded Event and Load Method

The Reloaded event occurs when the data for a tree has been reloaded, and its event handler merely calls the Load() method:

private void Load()
{
    Unload();

    if (m_view == null)
        return;

    if (m_treeListView.TheStyle == TreeListView.Style.VirtualList)
        m_treeListView.VirtualListSize = 0;

    // Add columns & data

    foreach (string columnName in m_view.ColumnNames)
        m_treeListView.Columns.Add(new TreeListView.Column(columnName));

    try
    {
        m_treeListView.BeginUpdate();

        if (m_treeListView.TheStyle != TreeListView.Style.VirtualList)
        {
            foreach (object root in m_view.Roots)
            {
                if (m_inTransaction)
                    m_queueInserts.Add(new ItemInsertedEventArgs<object>(-1, root, null));
                else
                    AddItem(root, null);
            }
        }
    }
    finally
    {
        m_treeListView.EndUpdate();
    }
}

Unload() clears the tree and all the work queues, such as m_queueInserts. The method TreeListView.ColumnCollection.Add() adds a column to the tree.

In the second loop, data items are obtained from the Roots property, which holds all the root data objects. This data is added to either to m_queueInserts or immediately added to the tree by AddItem(), as in the ItemInserted handler ObservableContextItemInserted shown in ItemInserted Event. For TreeListEditor, Roots is a property of DataContainer that enumerates the DataItem objects in this container.

In the end, TreeListView.EndUpdate() calls EndUpdate() on the underlying control, which resumes redrawing the tree. Drawing was prevented earlier by the TreeListView.BeginUpdate() method.

IValidationContext Events

IValidationContext provides events to encapsulate processes in transactions, so the operation can be cancelled if a problem occurs. These event handlers also modify tree data in a similar fashion to the IObservableContext events.

Beginning Event

The Beginning event handler simply sets flags:

private void ValidationContextBeginning(object sender, EventArgs e)
{
    m_inTransaction = true;
    m_treeListView.UseInsertQueue = true;
}

The m_inTransaction field is used throughout TreeListViewAdapter to determine whether a transaction is occurring, as seen in ItemInserted Event.

Ending and Ended Events

TreeListViewAdapter really handles only the Ended event signalling the end of a transaction, using this method:

private void ValidationContextEnded(object sender, EventArgs e)
{
    if (!m_inTransaction)
        return;

    try
    {
        m_treeListView.BeginUpdate();

        try
        {
            m_treeListView.UseInsertQueue = true;

            foreach (var args in m_queueInserts)
                AddItem(args.Item, args.Parent);
        }
        finally
        {
            m_treeListView.UseInsertQueue = false;
            m_treeListView.FlushInsertQueue();
        }

        foreach (var args in m_queueUpdates)
            UpdateItem(args.Item);

        foreach (var args in m_queueRemoves)
            RemoveItem(args.Item);
    }
    finally
    {
        m_inTransaction = false;

        m_queueInserts.Clear();
        m_queueUpdates.Clear();
        m_queueRemoves.Clear();

        m_treeListView.EndUpdate();
    }
}

ValidationContextEnded() first stops display drawing by calling BeginUpdate().

If there are items to add in m_queueInserts, it calls AddItem() for each one. Update items in m_queueUpdates are processed with UpdateItem(), and items in m_queueRemoves are removed with RemoveItem(). All the work queues are emptied.

Finally, EndUpdate() redraws the tree.

Topics in this section

Clone this wiki locally