Hierarchical change notifications in an object hierarchy - c#

I have a recursive hierarchy of three kinds of object in a C# library. Let's call them Boxes, Nuts and Bolts. Boxes can contain other Boxes, or Nuts and Bolts. Nuts and Bolts obviously can't contain anything.
Let's assume each Box has ObservableCollections of Box, Nut, and Bolt. Each Nut and Bolt implements INotifyPropertyChanged.
Is there an accepted best practice for propagating notifications of changes to the observable collections, or property changes on any Nut or Bolt to an object which holds a reference to the topmost Box? Or any particular design patterns you would recommend?
EDIT: to give some background to this issue, I lead the Chemistry for Word project. You can see the component that display structures in real time on the left.
Now, believe it or not, this currently draws everything through data binding. Each of those molecule displays on the LHS is an ItemsControl. (And yes, I am using WPF with MVVM!) This has proven to have too many overheads and lack of flexibility for a long-term solution. So I have gone back to generating DrawingVisuals directly. This approach allows much more fine control.
The Boxes, Nuts and Bolts in my original example are Molecules, Atoms and Bonds. If any of these are added, removed or changed then the display has to know about it so it can update. As I have already implemented the interfaces and objects for data binding then I want to exploit the code I already have.

I've had a similar model with fast-path access to upstream Node instances in directed acyclic graphs. A Node has a weak reference to its immediate parent. Node has a property to get the Root...that tries to return its parent's Root. If there's no parent, then that node is a root. Rootness is based solely on containment. Note that the parent isn't a collection...because sometimes the child node isn't even in a collection. Something more-or-less like...
public abstract class Node
{
WeakReference<Node> parent;
public Node Root
{
get { return Parent?.Root ?? this; }
}
public Node Parent
{
get
{
if ( parent != null )
{
if ( parent.TryGetTarget( out Node parentNode ) )
{
return parentNode;
}
}
return this;
}
internal set { /*...*/ } //--> if you're brave...
}
}
Edit:
Regarding WeakReferences...one of the things our graphs can have is references to nodes in other graphs. We have a node resolver service that will fetch those other nodes. These out-looking references are represented by an identity value (a GUID or a Long) and an associated weak reference. This way, we can load the specified node as needed, but not keep it any longer than necessary. The resolver maintains an LRU cache of nodes resolved this way.
If such a resolved reference needs to resolve its own parent, a similar mechanism exists to allow the dependent node to resolve its parent. Even a node's collected child nodes may be lazy loaded via the resolver service (although there are annotations that inform our framework when to lazy-load and when not).
So, the weak references help with all these incidentally-resolved scenarios. Er...more accurately, they help us not screw up the garbage collection in such scenarios.
In some analytical scenarios, we'll have hundreds of thousands of nodes popping in and out. I could imagine similar dynamics in chemistry modeling.

Why don't you call the parent in the change notification. Something like the following pseudo code:
Bolt()
{
NotifyPropertyChanged(property)
{
PropertyChanged(this, property);
}
ChildNutPropertyChanged(Nut child, property)
{
PropertyChanged(this, child + property);
}
}
Nut(Bolt parentBolt)
{
parent = parentBolt;
NotifyPropertyChanged(property)
{
PropertyChanged(this, property);
parent.NotifyPropertyChanged(this, property);
}
}

If you encapsulate your ObservableCollection of Nuts and Bolts and only make a ReadOnlyObservableCollection public, you could make an Add(Nut nut) method (and another one for bolts) that registers to the added Nut's NotifyPropertyChanged event.
This way, you'll know in the Box when a property of a child has changed and take action.

Related

MVVM: Extending a Read-Only Model with IsExpanded and IsSelected in TreeViewItem

Consider a ViewModel that exposes a tree defined in the Model, which is then data-bound to a TreeView. The tree is rather large and the model is used directly because it is essentially read-only with regards to the view. Now, the TreeView lives under a TabControl in the VisualTree, so an issue at this point is that the IsExpanded and IsSelected properties aren't preserved when switching between tabs. One hesitates to add these boolean properties to each node in the Model, as this should be extended in the ViewModel as a matter of principle. The tree is composed of polymorphic nodes, so if we were to create a ViewModel node type that derives from the tree node types and adds these properties, it seems this would result in some hairy code in the ViewModel:
That is, if the tree has an abstract NodeBase, and then derived types Node1, Node1, ... NodeN (the Model's Nodes). The ViewModel then has to encapsulate these nodes, so when creating a ViewModelNode, if it has a reference to Node and also references to child ViewModelNode's for each descendent ViewModelNode that encapsulates each descendent Model's Node all the way down the tree, maintaining these child references in the ViewModel identically to how they are maintained in the Model, along with a reference to the Model. i.e. all references in the Model nodes are replicated in the ViewModel nodes, in order for each Model node to be encapsulated by a ViewModel node. The existence of redundant references such as this, even if handled in the ViewModelNode's constructor, just smells bad.
What is the most accepted means to extend each node in a tree in this scenario, without wholesale replication of the references as stated above? (And to a lesser point, is the mere mention of using the model directly by the view an unforgivable crime, or is this forgiven due to the circumstances?)
There is perhaps an argument to be made that implementing those Boolean properties on the Model is OK, but personally I would look to creating ViewModels for each Model that's going to be in the TreeView. One advantage of doing so would perhaps be an increase in scalability, should you ever decide to implement more functionality related to the TreeView.
I think it depends on how much you are actually doing with the TreeView (within your app), but I do think the more you're doing, the stronger the argument for a ViewModel-based solution.
With regards to the hairy code, you could perhaps circumvent this to a degree by using an interface to describe your TreeView members, e.g.:
public interface IMyTreeViewItem
{
bool TreeViewItemIsSelected { get; set; }
bool TreeViewItemIsExpanded { get; set; }
// Further potential properties
string TreeViewItemHeaderText { get; set; }
List<IMyTreeViewItem> TreeViewItemChildren { get; set; }
}
This approach can be used to ensure that your TreeView members are properly "subscribed". There's also then an option to reference the interface type in XAML, for example, as the TargetType of a HierarchicalDataTemplate for the TreeView.

Extending the TreeView control for incremental filtering/searching

I'm trying to extend the winforms TreeView control to allow incremental filtering and searching similar to the Solution Explorer in VS2012/VS2013.
Ideally, I would like it to be capable of replacing the existing TreeView with minimal code change - as far as the consumer is concerned, the only difference would be a method void Filter(string). Because of this, I think it would make sense for the Nodes property to return the TreeNodeCollection with ALL nodes, even ones not showing because of an applied filter.
I have the code written to handle the filtering, and it actually works quite well except when I access base.Nodes, it returns my filtered nodes and not the full list.
The problem I have is, I'm unable to clone or create a new instance of TreeNodeCollection, because the constructor is marked as internal. So my ideal code would look something like this:
public class TreeViewEx : TreeView
{
// results in a compiler error:
private TreeNodeCollection _allNodes = new TreeNodeCollection();
public new TreeNodeCollection Nodes { get { return _allNodes; } }
public TreeNodeCollection FilteredNodes { get { return base.Nodes; } }
public void Filter(string searchString)
{
base.BeginUpdate();
base.Nodes.Clear();
foreach (TreeNode node in FilterInternal(_allNodes, searchString))
{
base.Nodes.Add(node);
}
base.EndUpdate();
}
}
So as you can see, I'm trying to decouple the nodes that are shown in the UI from the nodes that the consumer would access. Of course with TreeNodeCollection having an internal constructor only, I'm unable to create a new instance or clone it.
I considered these two options, but neither sound like good solutions:
Use reflection to instantiate the TreeNodeCollection object (due to the internal constructor) for the second list. This option seems like it would be more efficient than #2, but of course I'm creating an instance of an object I'm not supposed to.
Instantiate a second TreeView in memory and use the Nodes property from that to maintain my second list. This seems like it might be a lot of overhead.
I want the end result to still be a TreeNodeCollection so the TreeView can be used to replace our existing controls with minimal code and we do have several places using the Find method, which doesn't exist in List<TreeNode>.
Does anyone have any recommendations on how to handle this? What about performance/resource-wise with my two considerations?
Thank you
Update 1:
Per Pat's recommendation, I decided to take a step back and avoid messing with Nodes altogether. So now I've added a List<TreeNode> AllNodes property and have the Nodes just display the nodes that appear in the TreeView (the filtered list), so now it's a bit simpler.
My problem now is, how do I know when AllNodes has an item added to it so I can keep Nodes in sync? I've considered using a BindingList so I have the ListChanged event, but then I would need to have my TreeNode and node's children/grand-children/etc (AllNodes[0].Nodes) use a custom class that inherits from TreeNode and change the Nodes property, and TreeNode.Nodes isn't overridable. Is there another way? I could make a new property called NodeExs or something, but that seems very unintuitive and I could see another dev coming along later and pulling his hair out because the Nodes property is there but doesn't work.
With regard to your proposed solutions, #2 is out because a TreeNode cannot belong more than one control. And while it might be possible to create an instance of TreeNodeCollection via reflection, it won't be very useful because its designed to be coupled to a TreeView or another TreeNode. You won't be able to add/remove nodes from the collection.
Because of this, I think it would make sense for the Nodes property to
return the TreeNodeCollection with ALL nodes, even ones not showing
because of an applied filter.
I disagree, the TreeNodeCollection returned by the Nodes property is used by the framework and OS to render the control. You really don't want to hide this property or alter its functionality.
If a consumer needs to have access to _allNodes, create a List<TreeNode> AllNodes property or use a custom collection.
I've found out that the TreeNodeCollection should only be used to read the listed nodes. Instead, I've used List<TreeNode> to list nodes. In my project, I created a List<TreeNode> for each level on the TreeView. I filled the lists at the same time when I filled the TreeView, at the startup. In the end, I used AddRange() to make and combine a list of the all nodes. This way I had all the nodes listed and categorized.
It's easy and fast to create this kinds of lists. I also created a List<string> version of the all nodes list, which I set up as an AutoCompleteCustomSource for my TextBox. This way I was able to use TextBox with AutoComplete for searching the nodes.
I'd make different lists for the consumers and other categories. Then I'd only add the items to the TreeView which meet the given criteria. You can also use treeView.Nodes.Remove() to remove any nodes. You'd still have the actual node stored on the lists, and could add it back again later.
These are just some ideas.

How to deal with *many* context menus

I'm re-writing in C# (with Winforms) an old VB6 app that uses a single context menu with multiple Items that change their Caption, Visible, and Enabled traits based on a monolithic function called "InitControls"
The function is 500 lines long and consists primarily of a switch statement that decides what controls to enable based on the selected item's tag (there's a tree view and list view; it selects the selected item from the active one and gets its tag). It then enables, disables, and modifies the text of the visible items, and clears any useless Separators. The original uses ActiveBar (a custom control) which allows it to change the text in one place and display the item in menus, context menus, and toolbars all at once.
I'm currently just re-implementing the logic line for line in C#, but I hate it because I'm not really fixing anything, just putting the problem into a new language (and possibly screwing it up in the process). I created a class that allowed me to change the text, enabled and visible properties of any "subscribed" Menu Items in one place and even add/remove event handlers for all subscriBed menu items. It works, and even seems apparently correct, but I'm pretty sure there's got to be a better way. My MainForm is ENORMOUS.
What is the standard .NET way of handling complex Context Menu and Toolbar logic?
From what I understand, you basically want to refactor a large switch-case method. Googling for "switch case refactoring" should give you several examples you can check out to find something that suits you best.
Usually, when you are refactoring a switch case, this means that you want to extract logic from each case block into a new class, possibly an implementation of an interface common to all cases. The right implentation of your class will depend on the condition of an individual case statement: this is called a Strategy pattern, because each condition demands a different strategy.
In your case, you need to slightly extend the pattern: you have a number of candidates for the context menu, each of them being able to handle a certain node type. In that case, your right-click handler needs to let them decide if they can provide functionality for a certain node.
[Edit]
To clarify a bit, I will provide a simple example.
I mentioned that individual implementations should be extracted into classes which implement the same interface, which should be responsible for changing menu items' appearance and state, based on the current condition.
interface IMenuStateManager
{
// this method updates state of one or
// more menu elements, according to the
// specified selected node info
void UpdateState(ISelectedNodeInfo info);
}
Our first, basic implementation of the IMenuStateManager interface will do nothing more that simply call other managers' implementations. This is called a Composite object pattern, because it allows us to treat a group of objects as a single object:
// composite class for a list of menu managers
class CompositeMenuStateManager : IMenuStateManager
{
private readonly IMenuStateManager[] _childManagers;
// params keyword will allow as to pass a comma separated list
// of managers, which is neat
public CompositeMenuStateManager(params IMenuStateManager[] managers)
{
_childManagers = managers;
}
// this is where the job gets done, but composite
// class doesn't do much work by itself
public void UpdateState(ISelectedNodeInfo info)
{
// allow each state manager to change its state
foreach (IMenuStateManager mgr in _childManagers)
{
mgr.UpdateState(info);
}
}
}
Now, you still have an enormous list of possible menu candidates, but now their logic is separated into different classes, and then wrapped in a single composite object.
IMenuStateManager _menuManager = new CompositeMenuStateManager
(
// note: each menu "manager" can manage one or more
// items, if you find it useful.
// For example, ClipboardMenuStateManager can be
// a composite manager itself (cut/copy/paste).
new ClipboardMenuStateManager(some params),
new SomeOtherMenuItemManager(various params),
new YetAnotherMenuItemManager(various params),
...
);
I guess that menu states get updated when a node is selected, but this is something you should easily adapt to your app. That particular event handler delegates the whole responsibility to our composite menu manager:
void Node_Selected(sender object, EventArgs args)
{
// find out which node was clicked
Node node = object as Node;
// get the data (model) node for this tree node
INodeData data = node.Tag as INodeData;
// create some info which will be passed to the manager.
// you can pass information that might be useful,
// or just simply pass the node data itself
ISelectedNodeInfo info = new SelectedNodeInfo(data, some other stuff);
// let the manager do the rest of the job
_menuManager.UpdateState(info);
}
Since you will probably have three menu items doing the same job at the same time (main menu, context menu, toolbar), you will probably want to make each IMenuStateManager implementation update all three of them at the same time. The simplest way should be to to pass an array of ToolStripItem objects, which is the base abstract class for several different menu elements:
class PrintMenuManager : IMenuStateManager
{
private readonly ToolStripItem[] _items;
// this constructor can accept several menu elements
public PrintMenuManager(params ToolStripItem[] items)
{
_items = items;
}
public void UpdateState(ISelectedNodeInfo node)
{
foreach (ToolStripItem item in _items)
{
// if node is printable, enable
// all "print" menu items and buttons
item.Enabled = (node.IsPrintable);
}
}
}
When creating the PrintMenuManager instance, you can pass all buttons and menu items which are related:
// (this should be one of the child managers in
// the composite menu manager, but you get it)
IMenuStateManager printMnuManaegr = new PrintMenuManager
(
this.printMenuItem,
this.printContextMenuItem,
this.printToolbarButton,
);
Whew, this turned out to be a lengthy one at the end. :)
Ok, that's about it for a start.

Add editing to MVVM in a hierarchical data structure

This question is a follow-up of this older one, and it's more of a confirmation than an open question.
My ViewModel instance has a private instance of the Model, _modelInst.
The ViewModel has exclusive access to the Model's data during editing (so the Model doesn't need to implement INotifyPropertyChanged).
Now there are three ways I came up with how to edit the Model data from the View:
Getting/setting directly on the Model instance
e.g. for simple value fields
return _modelInst.fieldname;
_modelInst.fieldname = value;
This one's easy to implement...
Creating a ViewModel instance and operating on the parent's data structure
e.g. for more complex object types like structs:
Creating a new ViewModel for that type.
The ViewModel knows the parent and its fieldname.
displaying that in a ContentControl+DataTemplate
getting / setting:
via methods of the parent with the fieldname as parameter,
overwriting the whole original object even if only one field is changed
This means creating a new interface (with update routines working on _modelInst), implemented by the parent, for each of these structures.
Creating ViewModel instances with no direct knowledge of the parent's data structure
e.g. for (lists of) classes within parent classes
Creating a new ViewModel for each class
Sending update instructions to the parent via
commands
messages
reflection (parent knows which child called the function
by comparing the instance to all stored children)
All of these are a big mess implementing, creating functions for
every field of the model that is editable.
Which means pretty much all fields of the model...
(4.) One could create a generic ViewModel which works via reflection alone, where each
subobject knows its parent and its fieldname (+index, if in a list).
Only the root's logic would then interfere with the model.
But that solution would also require a means to store the path to a field within _modelInst.
Is there any other (more simple) way to achieve this?
Did I misunderstand the principles of MVVM (again)?
Is MVVM suited for manipulation of large hierarchical data structures?
Hopefully these resources will help; they helped me quite a bit as I learned MVVM and how to approach representing object graphs/hierarchies with view models:
Editable Object Adapter
Editable Collection Adapter
MicroModels
This is an excellent question for which I do not feel there is a good answer that comes stock with the MVC pattern.
ViewModels work great when the model they map to has no children.
But when the model has children, as in
Customer
-->Order
-->Country
(imagining Country were a child object of Customer) the design pattern kind of breaks down.
The best thing I've found is to use inheritance and selectively expose
only those children for which you need viewmodel logic. Otherwise, just access
the model's properties of the view that will come in via inheritance.
public class CustomerView : Customer //inherits from Customer (model)
{
public CustomerView(Customer customer)
{
this.FirstName = customer.FirstName
//etc..
//Only if you need it, that is if you have some display-specific
//logic relating to country for a given view, you create
//a CountryView class that inherits from Country and gets populated
//by an instance of it as well
this.CountryView = new CountryView(customer.Country)
}
public CountryView CountryView {get;set;} //sadly you cannot override Country but you may be able to shadow it.
public string DisplayColor
{
if(base.FirstName == "Joe")
{
return "red";
}
return "";
}
}
This gets messy when dealing with grandchildren. If anyone has a better solution, I would love to hear it.
Thanks

IHierarchyData and IHierarchicalEnumerable in Winforms

Currently,I know how to do a lazy implementation of the loading procedure of the nodes in a treeview control, and read the related questions in stackoverflow, but I'm also reading about IHierarchyData and IHierarchicalEnumerable interfaces in asp.net (I didn't know to code asp.net) that allow to bind a collection to a treeview in order to display the items automatically.
It would like to know if I can do the same in winforms and C#. I think that the interfaces previous mentioned are not available in winforms.
Thanks.
The Windows Forms TreeView does not know how to bind to an IHierarchyData instance, which isn't surprising given that the IHierarchyData and related interfaces are intended for consumption by web controls (especially site maps).
However, it's really not too hard to build your own data binding class. This seemed like an interesting problem so I threw one together just for fun. I'll walk you through the inner workings.
First, create a basic Component class. Visual Studio will start you off with code like this:
public partial class TreeViewHierarchyBinding : Component
{
public TreeViewHierarchyBinding()
{
InitializeComponent();
}
public TreeViewHierarchyBinding(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
One obvious piece of "state" this component needs to have is a mapping from each TreeNode to its IHierarchyData. Now we can hack around this by throwing it in the TreeNode's Tag property, but let's aim to make this component as non-invasive as possible and keep track of its own state. Hence, we'll use a dictionary. Add this field to the class:
private Dictionary<TreeNode, IHierarchyData> nodeDictionary = new
Dictionary<TreeNode, IHierarchyData>();
Now, at a minimum, this component needs to know how to populate a specific parent TreeNode of a TreeView class from its correspondingly bound IHierarchyData, so let's write that code next:
private void PopulateChildNodes(TreeNodeCollection parentCollection,
IHierarchicalEnumerable children)
{
parentCollection.Clear();
foreach (object child in children)
{
IHierarchyData childData = children.GetHierarchyData(child);
TreeNode childNode = new TreeNode(childData.ToString());
if (childData.HasChildren)
{
childNode.Nodes.Add("Dummy"); // Make expandable
}
nodeDictionary.Add(childNode, childData);
parentCollection.Add(childNode);
}
}
private void UpdateRootNodes(TreeView tv, IHierarchyData hierarchyData)
{
if (tv == null)
{
return;
}
tv.Nodes.Clear();
if (hierarchyData != null)
{
IHierarchicalEnumerable roots = hierarchyData.GetChildren();
PopulateChildNodes(tv.Nodes, roots);
}
}
This part should be pretty straightforward. The first method just populates a TreeNodeCollection (i.e. the Nodes property of a TreeNode) with the hierarchy obtained from an IHierarchyData instance, using the IHierarchyEnumerable interface. The only really interesting things this method does are:
Adding a dummy node when the IHierarchyData instance has children; this makes the "+" visible in the tree view, otherwise we wouldn't be able to expand any deeper; and
Adding the newly-added node to the dictionary with the IHierarchyData instance it matches with.
The second method is even simpler, it does the initial "binding work", replacing whatever is in the root of the tree with our top-level IHierarchyData instance.
The next thing our component needs to be able to do is hook the loading events from the TreeView to perform lazy-loading. Here's the code to do that:
private void RegisterEvents(TreeView tv)
{
tv.BeforeExpand += TreeViewBeforeExpand;
}
private void UnregisterEvents(TreeView tv)
{
tv.BeforeExpand -= TreeViewBeforeExpand;
}
private void TreeViewBeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Checked)
{
return;
}
IHierarchyData hierarchyData;
if (nodeDictionary.TryGetValue(e.Node, out hierarchyData))
{
PopulateChildNodes(e.Node.Nodes, hierarchyData.GetChildren());
e.Node.Checked = true;
}
}
First two methods should be self-explanatory, and the third method is the actual lazy-loading code. We're cheating a little here, using the TreeNode.Checked property to delineate whether or not the child nodes have already been loaded so we don't do any unnecessary reloads. I always do this when I implement lazy-loaded trees because, in my experience, I almost never use the TreeNode.Checked property. However, if you do need to use this property for something else, you can either use a different property (like Tag), create another dictionary to hold the expanded states, or modify the existing dictionary to hold a composite class (containing the IHierarchyData as well as an Expanded property). I'm keeping it simple for now.
The rest should already make sense to you if you've implemented lazy-loading in a tree before, so let's skip ahead. Really the only thing left to do at this point is implement some designer/user properties that will actually wire up the tree and data:
private IHierarchyData dataSource;
private TreeView treeView;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IHierarchyData DataSource
{
get { return dataSource; }
set
{
if (value != dataSource)
{
dataSource = value;
nodeDictionary.Clear();
UpdateRootNodes(treeView, value);
}
}
}
[Category("Behavior")]
[DefaultValue(null)]
[Description("Specifies the TreeView that the hierarchy should be bound to.")]
public TreeView TreeView
{
get { return treeView; }
set
{
if (value != treeView)
{
if (treeView != null)
{
UnregisterEvents(treeView);
}
treeView = value;
nodeDictionary.Clear();
RegisterEvents(value);
UpdateRootNodes(treeView, dataSource);
}
}
}
Easy peasy. We've got a DataSource property that accepts the root IHierarchyData, and a TreeView property which you'll be able to access from the designer. Again, simple stuff here, when the DataSource property is updated, we just reset the lookup and repopulate the root. When the TreeView property is updated we have to do a little more work, registering the events, making sure to unregister events from the old tree view, and doing all the same stuff we do when the data source changes.
That's really all there is to it! Open up the Windows Forms designer, drop a TreeView, then drop a TreeViewHierarchyBinding and set its TreeView property to the tree view you just dropped. Finally, in your code somewhere (i.e. in the Form_Load event), give it a data source:
private void Form1_Load(object sender, EventArgs e)
{
DirectoryInfo dir = new DirectoryInfo("C:\\");
treeViewHierarchyBinding1.DataSource = new FileSystemHierarchyData(dir);
}
(Note - this uses the example FileSystemHierarchyData that's on the MSDN page for IHierarchyData. The example isn't very robust, it doesn't check for UnauthorizedAccessException or anything, but it's good enough to demonstrate this).
And that's it. Run your app and watch it bind. You can now reuse the TreeViewHierarchyBinding component anywhere - just drop it on a form, assign it a TreeView, and give it an IHierarchyData instance as a data source.
I've put the complete code on PasteBin if you want a copy-and-paste version.
Have fun!
The interfaces are available, but will require you to add a reference to System.Web.UI. (It might also require you to use the full .NET Framework redistributable rather than the Client Profile, although I'm not certain about that.)
The larger question is: Does the WinForms TreeView control automatically understand how to work with these interfaces? I believe the answer to that question is "No", but you would need to test/verify that.
There's an interesting article here that shows you how to build extension methods to achieve what I think you're looking for. There is no native availability within the System.Windows.Forms.TreeView to bind to a collection from what I can find.
You CAN include System.Web.UI in your project to make the IHierarchyData and IHierarchicalEnumerable interfaces available, but the TreeView will not be able to attach to them without the extension methods.
The sample source code from the web site will let you bind any IDictionary collection to the TreeView.

Categories