views:

2323

answers:

5

I'm trying to use a TreeView to display a tree structure of objects. I have a tree of four types of object, Company (the root node), City, Store and Employee.

The interface is designed to add/remove Cities/Stores/Employees, so the TreeView has to update to reflect any changes.

I am wondering on the correct way to get the TreeView to display the tree structure, and receive updates when it changes.

I am thinking that the Company object should have events, such as company.CityAdded and company.CityRemoved, then whatever wrapper I put round the TreeView responds to those events? When the TreeView has been built up, there will be a node per city/store/employee. Each node could then respond to events of the node it represents in the tree.

Is that the right idea? Or is there a better method?

+1  A: 

You're on the right lines about the concept of listening to events (it's a standard publisher/subscriber pattern).

For the actual updating of the treeview I tend to have two methods: AddOrUpdateTreeItem, and RemoveTreeItem. The add or update method does what it says, looks for the tree item (based on a path) and updates it or adds it. Of course if the model is being updated on a thread other than the one the form was created on you will need to marshal the call using Control.BeginInvoke().

This approach can be a little slow if you're populating a full tree at form_load or something, so you might have a different method for an initial population, and use the concept I've described here for subsequent updates.

I do the same for listviews, here's a typical example. The main difference when adding a tree item is that you might need to add the parent nodes in order to add the node being requested, which makes it a bit recursive. Give it a try.

private void AddOrUpdateListItem(DomainModelObject item)
{
    ListViewItem li = lvwListView.Items[GetKey(item)];

    if (li == null)
    {
     li = new ListViewItem
        {
         Name = GetKey(item), 
         Tag = item
        };
     li.SubItems.Add(new ListViewItem.ListViewSubItem());
     li.SubItems.Add(new ListViewItem.ListViewSubItem());
     li.SubItems.Add(new ListViewItem.ListViewSubItem());
     li.ImageIndex = 0;
     lvwListView.Items.Add(li);
    }

    li.Text = [Itemtext];
    li.SubItems[1].Text = [Itemtext];
    li.SubItems[2].Text = [Itemtext];
    li.SubItems[3].Text = [Itemtext];
}

Here's an example of how BeginInvoke() might be implemented:

public class MyForm : Form
{
    ...

    void data_Changed(object sender, DataChangedEventArgs e)
    {
     if (this.InvokeRequired)
     {
      this.BeginInvoke(new EventHandler<DataChangedEventArgs>(data_Changed), sender, e);
      return;
     }

     AddOrUpdateListItem(e.DataItem);
    }

    ...
}
Neil Barnwell
+1 for mentioning BeginInvoke(). You may want to modify your example to show that however.
Matt Jordan
+1  A: 

Sounds like you are on the right path. I had to do similar thing, a few pointers I'd like to share:

  1. Store object reference in TreeNode tag property.

  2. Give each Treenode a unique name that can easily identify an object, for example: object hashcode, Company ID, etc.

This way you can easily find and update TreeNode when object state changes. And when user selects a node, you can grab an object it's representing from the Tag property.

Good luck.

WebMatrix
A: 

Instead of ...

  • Business objects subscribe to UI events
  • Commands update the UI
  • Business objects are updated when the UI is updated

... you can also do it the other way around (i.e. commands update the tree of business objects, which results in a corresponding update to the UI).

ChrisW
+1  A: 

I just wanted to add that if WPF is an option for this, it becomes incredible simple using heirarchtical databinding and observablecollections. It basically does all the event handle stuff for you, and lets you just interact with your business objects.

Jacob Adams
A: 

Part of the key to the publish/subscribe pattern for updates is how to wrap the information of what to do when the event triggers.

When the object representing "Store X" is updated with a new name, and fires an event to announce this has happened, which object consumes the event?

Similarly, when City Y is added, which object should be notified of the creation?

One common approach is to have some kind of large uber-manager class that handles the entire process - it subscribes to all the events and does everything.

A different approach, one I've used to good effect, is to create much simpler wrapper/coordinator objects that handle just one part of the puzzle. Typically, I suffix the name of these classes with "Editor".

So, you could have a CityEditor class whose constructor takes both a City object, and the TreeNode that represents that object. The CityEditor would subscribe to events on both the City object and the TreeNode, and would take care of populating the TreeNode with a caption and selecting an icon.

When the City object updates, the CityEditor responds to the triggered event by updating the TreeNode. When the City object is removed, the CityEditor makes sure the node is removed from the Treeview.

When a new Store object is added to the City, the CityEditor can take care of creating a StoreEditor to coordinate updates at that level. Similarly, when an Employee is added to a Store, an instance of EmployeeEditor handles updates of the Treeview.

Bevan