Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

XML TreeView Control

0.00/5 (No votes)
20 Oct 2005 1  
A control to display XML-data and/or make it editable, avoiding the unmanaged Internet Explorer plug-in.

XML TreeView

Introduction

Have you ever been in a situation where you wanted to display XML-data to your users and/or make it editable? To achieve this, one might first think of the (in .NET 1.0 unmanaged) Browser COM plug-in. It does the display part quite well, although the unnerving JavaScript-blocker on WinXP SP2 spoilt that a bit. Also, you might really require editing capabilities as well and that plug-in does not really help you that much, does it?! Also, when allowing your users to edit the XML-data it might be quite nice to get some sort of validation of what the user enters. Moreover, you can't be bothered writing all that printing code yourself when you can have a control that handles all of that, much like the Internet Explorer plug-in.

Well, hopefully, this control might help you a bit. Feel free to change it, extend it, customize it or simply use it in your application.

The XML TreeView offers the following built-in functionality:

  • Printing of an XML document.
  • Editing XML nodes.
  • Deleting XML nodes.
  • Searching an XML document.
  • Validating XML data entered by the user.
  • Saving XML to file.
  • Copying and pasting XML data.
  • Dragging and dropping XML files onto the control.
  • Opening XML files through OpenFileDialog.

Using the component in your code

Simply download the Zip-file containing the binary, extract the assembly to your local hard drive, and install it in your Visual Studio .NET Toolbox. That's it! You're ready to use the component in your Windows Forms applications.

There are only two properties of interest when using the control in your code, i.e. the property "LabelEdit" and the property "Xml" of type XmlTreeView. The value of the boolean property "LabelEdit" decides whether the tree nodes can be edited and deleted whereas the string property "Xml" allows you to programmatically retrieve and set the XML-data displayed by the control. Functionality can usually be accessed in two ways, that is through a pop-up menu which is contained in the XML-TreeView, or through keyboard action, e.g. Ctrl+P to trigger the printing of a document.

Getting to grips with the source code

First of all, we need a way to abstract from the System.Windows.Forms.TreeView because we need to cater for error situations when it makes simply no sense to display the erroneous data in a tree structure. Therefore, we require a container control which makes the decision as to which type of child control is best suited to display the data loaded into the XML-control. This task is assumed by the type Tom.XmlControls.XmlTreeView which derives from System.Windows.Forms.UserControl and publishes the aforementioned properties LabelEdit and Xml to the outside world. This container creates an instance of Tom.XmlControls.InnerXmlTreeView - after all this is a control to display XML, so this is the default-control - and attempts to assign the data to its Xml-property. If this causes an exception to be thrown, the container will create an instance of Tom.XmlControls.InnerTextBox and assign it the data. Obviously the latter will then not display it in a tree structure but rather as plain text. In order to bind the two types InnerXmlTreeView and InnerTextBox to a sort of implementation contract, they must implement the interface Tom.XmlControls.IXmlControl so as to hide the fact from the user that controls are dynamically swapped in and out.

Below is the definition of this interface:

using System;
using System.Drawing.Printing;

namespace Tom.XmlControls
{
    internal interface IXmlControl
    {
#region Content
        string Xml { get; set; }
        #endregion // Content


#region Searching
        void Search( object sender, EventArgs e );
        void StartSearch( string criterion, bool caseSensitive );
        void Next();
#endregion // Searching


#region Printing
        void Print( object sender, EventArgs e );
        void PrintPage( object sender, PrintPageEventArgs e );
        PrintDocument PrintDocument { get; }
#endregion // Printing


#region Opening, Editing, Deleting, Copying, Pasting
        void Edit(object sender, System.EventArgs e);
        void Delete(object sender, System.EventArgs e);
        void Copy(object sender, System.EventArgs e);
        void Paste(object sender, System.EventArgs e);
#endregion // Edititing, Deleting, Copying, Pasting


#region Saving, Opening
        void Save(object sender, System.EventArgs e);
        void Open(object sender, System.EventArgs e);
#endregion // Saving, Opening

    }
}

The two implementations of this interface are InnerXmlTreeView and InnerTextBox. Unfortunately, not all of the expected contract can be enforced by the interface. This holds especially true for inherited event handlers which have to be overridden such as those for handling drag/drop events:

protected override void OnDragEnter( DragEventArgs e )
{
    base.OnDragEnter( e );

    if ( e.Data.GetDataPresent( DataFormats.FileDrop ) )
    {
        e.Effect = DragDropEffects.Copy;
        draggedFile = (string) ( (object[]) 
                       e.Data.GetData( DataFormats.FileDrop ) )[0];
    }
}

protected override void OnDragDrop( DragEventArgs e )
{
    base.OnDragDrop( e );

    if ( draggedFile.Length > 0 )
    {
        using ( StreamReader fs = new StreamReader( draggedFile ) )
        {
            parent.Xml = fs.ReadToEnd();
        }
        
        draggedFile = string.Empty;
    }
}

Assuming that we actually have valid XML-data (remember, we are not talking about schema-validation) which can be loaded into a DOM, we'll have to parse the DOM and build the tree structure of the TreeView accordingly. This is done by the following method which is called recursively:

private void RecurseAndAssignNodes( XmlElement elem )
{
    string attrs = string.Empty;
    XmlTreeNode addedNode = null;

    foreach ( XmlAttribute attr in elem.Attributes )
    {
        attrs += " " + attr.Name + "=\"" + attr.Value + "\"";
    }

    if ( elem.Equals( document.DocumentElement ) )
    {
        addedNode = new XmlTreeNode( "<" + elem.Name + attrs + ">", elem );
        Nodes.Add( addedNode );
        InnerXmlTreeView.CurrentNode = addedNode;
        Nodes.Add( new XmlTreeNode( "</" + elem.Name + ">", null ) );
    }
    else
    {
        if ( elem.HasChildNodes && 
             elem.ChildNodes[0].NodeType == XmlNodeType.Text )
        {
            addedNode = new XmlTreeNode( "<" + elem.Name + attrs + 
                        ">" + elem.InnerText + "</" + 
                        elem.Name + ">", elem );
            InnerXmlTreeView.CurrentNode.Nodes.Add( addedNode );
            InnerXmlTreeView.CurrentNode = addedNode;
        }
        else
        {
            if ( elem.IsEmpty )
            {
                addedNode = new XmlTreeNode( "<" + 
                                elem.Name + attrs + "/>", elem );
                InnerXmlTreeView.CurrentNode.Nodes.Add( addedNode );
                InnerXmlTreeView.CurrentNode = addedNode;
            }
            else
            {
                addedNode = new XmlTreeNode( "<" + elem.Name + attrs + ">", elem );
                InnerXmlTreeView.CurrentNode.Nodes.Add( addedNode );
                InnerXmlTreeView.CurrentNode = addedNode;
                InnerXmlTreeView.CurrentNode.Parent.Nodes.Add( new 
                    XmlTreeNode( "</" + elem.Name + ">", null ) );
            }
        }
    }

    XmlElement nextElement = null;

    foreach ( XmlNode child in elem.ChildNodes )
    {
        if ( ( nextElement = child as XmlElement ) != null )
        {
            RecurseAndAssignNodes( nextElement );
        }
        else if ( child.NodeType == XmlNodeType.Comment )
        {
            InnerXmlTreeView.CurrentNode.Nodes.Add( child.OuterXml );
        }
    }

    if ( InnerXmlTreeView.CurrentNode.Parent != null )
    {
        InnerXmlTreeView.CurrentNode = InnerXmlTreeView.CurrentNode.Parent;
    }
}

Points of Interest

Recursion is of vital importance in this context and a powerful technique to parse any tree-like structure such as XML, directory trees and so on. However, it bears its pitfalls especially when it comes to local method variables and parameters. But most of the time, storing information at instance level will remedy these problems. Nonetheless, recursion can have a severe performance impact and is generally discouraged, unless you're programming LISP, because it is equally annoying to debug.

History

  • Released version 1.0.0.0 on 5th August 2005.
  • Released Version 1.0.0.1 on 9th August 2005 [Bug fix: Edit-mode InnerXmlTreeView].
  • Released Version 1.0.0.2 on 2nd September 2005 [Bug fix: XML-Comments].
  • Released Version 1.0.0.3 on 23rd September 2005 [Bug fix: parent controls other than forms].
  • Released Version 1.0.0.4 on 10th October 2005 [New public property XmlTreeView.XmlFile takes string path to a file containing XML-data].

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here