I\'m trying to import an XML file of nodes into the same node structure in a TreeView using C#. I have found a lot of example that use a single node structure, but have had a lo
The reason this is complicated is that your tree node hierarchy does not correspond 1-1 to your XML hierarchy. In situations like this, I suggest introducing intermediate classes to provide a view into the base XML model data. In WPF these classes would be the View Model, but the windows forms TreeView doesn't support data binding. Despite this, the abstraction of a view model is useful here.
First, some basic view model interfaces and classes to tie together TreeNode
and XElement
hierarchies:
public interface ITreeNodeViewModel
{
string Name { get; }
string Text { get; }
object Tag { get; }
object Model { get; }
IEnumerable Children { get; }
}
public abstract class TreeNodeViewModel : ITreeNodeViewModel
{
readonly T model;
public TreeNodeViewModel(T model)
{
this.model = model;
}
public T Model { get { return model; } }
#region ITreeNodeProxy Members
public abstract string Name { get; }
public abstract string Text { get; }
public virtual object Tag { get { return this; } }
public abstract IEnumerable Children { get; }
#endregion
#region ITreeNodeViewModel Members
object ITreeNodeViewModel.Model
{
get { return Model; }
}
#endregion
}
public abstract class XElementTreeNodeViewModel : TreeNodeViewModel
{
public XElementTreeNodeViewModel(XElement node) : base(node) {
if (node == null)
throw new ArgumentNullException();
}
public XNamespace Namespace { get { return Model.Name.Namespace; } }
public override string Name
{
get { return Model.Name.ToString(); }
}
}
Next, a couple extension classes:
public static class TreeViewExtensions
{
public static void PopulateNodes(this TreeView treeView, IEnumerable viewNodes)
{
treeView.BeginUpdate();
try
{
treeView.Nodes.PopulateNodes(viewNodes);
}
finally
{
treeView.EndUpdate();
}
}
public static void PopulateNodes(this TreeNodeCollection nodes, IEnumerable viewNodes)
{
nodes.Clear();
if (viewNodes == null)
return;
foreach (var viewNode in viewNodes)
{
var name = viewNode.Name;
var text = viewNode.Text;
if (string.IsNullOrEmpty(text))
text = name;
var node = new TreeNode { Name = name, Text = text, Tag = viewNode.Tag };
nodes.Add(node);
PopulateNodes(node.Nodes, viewNode.Children);
node.Expand();
}
}
}
public static class XObjectExtensions
{
public static string TextValue(this XContainer node)
{
if (node == null)
return null;
//return string.Concat(node.Nodes().OfType().Select(tx => tx.Value)); c# 4.0
return node.Nodes().OfType().Select(tx => tx.Value).Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
}
public static IEnumerable Elements(this IEnumerable elements, XName name)
{
return elements.SelectMany(el => el.Elements(name));
}
}
Next, view models for the three levels of your tree:
class ContainerViewModel : XElementTreeNodeViewModel
{
public ContainerViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ContainerName").TextValue();
}
}
public override IEnumerable Children
{
get { return Enumerable.Empty(); }
}
}
class ClassificationViewModel : XElementTreeNodeViewModel
{
public ClassificationViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ClassificationName").TextValue();
}
}
public override IEnumerable Children
{
get
{
return Model.Elements(Namespace + "Containers").Elements(Namespace + "Container").Select(xn => (ITreeNodeViewModel)new ContainerViewModel(xn));
}
}
}
class GroupViewModel : XElementTreeNodeViewModel
{
public GroupViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "GroupName").TextValue();
}
}
public override IEnumerable Children
{
get
{
return Model.Elements(Namespace + "Classifications").Elements(Namespace + "Classification").Select(xn => (ITreeNodeViewModel)new ClassificationViewModel(xn));
}
}
}
Now, building your tree becomes quite simple:
var xdoc = XDocument.Load("ProductDocument.xml");
var ns = xdoc.Root.Name.Namespace;
treeView1.PopulateNodes(xdoc.Root.Elements(ns + "ProductGroup").Elements(ns + "Group").Select(xn => (ITreeNodeViewModel)new GroupViewModel(xn)));
And the result:
Later, if you wish to add editing functionality to your tree, you can add the appropriate methods to ITreeNodeViewModel
-- for instance, a setter method for Text
. Since the ITreeNodeViewModel
has saved itself in the TreeNode.Tag the appropriate methods will be available.