0

How to display folder structure from xml file to wpf treeview? I have tried second part (Regions) of Josh Smith's article http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewModel but, how to scan or display a xml file containing folder structure with unknown number of levels?

Below is the xml file I want to display in wpf treeview. Is making name an attribute instead of an element a better practice?

<?xml version="1.0" encoding="utf-8"?>
<Folder>
  <Folders />
  <FolderName>
    <dir>
  <name>RootDir</name> 
  <file>
    <name>dobatchfile.bat</name>
  </file>

  <dir>
    <name>Build</name>
    <dir>
      <name>BuildProcessTemplates</name>
      <file>
        <name>AzureContinuousDeployment.11.xaml</name>
      </file>
      <file>
        <name>DefaultTemplate.11.1.xaml</name>
      </file>

    </dir>

  </dir>

</dir>
  </FolderName>
</Folder>

P.S. Below is my unsuccessful attempt. I am tring to save the xml file contents into a List to bind to my wpf treeview using mvvm pattern.

public static List<Directory> GetRegions()
        {
            List<Directory> ret = new List<Directory>();
            //var expandolist = GetExpandoFromXml("C:\\New folder/Region1.xml", "Regions", "");
            var expandolist = GetExpandoFromXmlRoot("c:\\temp/SerializationOverview.xml", "Regions", "");            
            expandolist.ToList().ForEach(element =>
            {
                var dictionary = element as IDictionary<string, object>;
               // dictionary.ToList().ForEach(d => ret.Add(new Directory(d.Key)));
                dictionary.Where(d => d.Key == "name" || d.Key == "dir").ToList().ForEach(d => ret.Add(new Directory(d.Value.ToString())));                
            });
            return ret;
        } 

 public static IEnumerable<dynamic> GetExpandoFromXml(string file, string descendantid, string Selection)
        {
            var expandoFromXml = new List<dynamic>();
            var doc = XDocument.Load(file);           
                //foreach (var element in doc.Root.Descendants(descendantid))
            foreach (var element in doc.Root.Descendants())
                {
                    dynamic expandoObject = new ExpandoObject();
                    var dictionary = expandoObject as IDictionary<string, object>;
                    foreach (var child in element.Elements().Where(e =>   e.Parent.Parent.Value.Contains(Selection)))
                    //foreach (var child in element.Descendants())
                    {
                        if (child.Name.Namespace == "")
                            dictionary[child.Name.ToString()] = child.Value.Trim();
                          //  dictionary[child.Name.ToString()] = child.Attributes().FirstOrDefault().Value;
                    }
                    yield return expandoObject;
                }            
        }
5
  • Have you already read the XML file into some data structure (if so, please show the code)? Or, is reading the XML file your actual problem? Commented Nov 14, 2013 at 22:29
  • @elgonzo Reading the XML file is my actual problem. Commented Nov 14, 2013 at 22:31
  • Okay, some more questions :) Do you need the information from the XML file only for your TreeView, or do you need the XML data also somewhere else in your software? Does the TreeView only displays the text from the XML, or will the XML contain some attributes/elements that have an effect on the visualization of the TreeViewItems? Commented Nov 14, 2013 at 22:34
  • @elgonzo I need xml data only for treeview and the xml file might contain some attributes that have an effect on the visualization of the TreeViewItems. Commented Nov 14, 2013 at 22:43
  • Whether you use sub-elements instead of attributes depends on your needs. Rule of thumb: Design your XML format as clean, as readable, and as unambiguous as possible. Often that means using attributes for information such as "name", "date", etc. is the better approach. However, from a mere software standpoint (a-ka Linq queries), there is no dramatic difference between these two approaches.. Commented Nov 15, 2013 at 2:16

1 Answer 1

3

To display the data from your XML file in a TreeView, the data will be organized in a data model, which makes its manipulation and representation through the TreeView easy.


1. The data model

Every entry in the tree will derive from a common base type.

public abstract class NamedTreeEntry
{
    public string DisplayName { get; set; }
}

Since all entries will have to display a name, an appropriate property DisplayName is also declared within the base type.

For our case, two concrete types deriving from that base type have to be considered: directories and files.

public class FileEntry : NamedTreeEntry
{
    // ... and other file-specific public properties and methods
}

public class DirectoryEntry : NamedTreeEntry
{
    public ObservableCollection<NamedTreeEntry> ChildEntries
    {
        get { return _collChildren; }
    }
    private readonly ObservableCollection<NamedTreeEntry> _collChildren;

    public DirectoryEntry(IEnumerable<NamedTreeEntry> childEntries)
    {
        _collChildren = (childEntries != null) ?
            new ObservableCollection<NamedTreeEntry>(childEntries)
            : new ObservableCollection<NamedTreeEntry>();
    }

    // ... and other directory-specific public properties and methods
}

Note the usage of the ObservableCollection<T> type for the children list. Although not strictly necessary for the example outlined here, ObservableCollection<T> allows dynamically adding or removing child entries while the TreeView control is being kept up-to-date automatically.


2. Transforming the XML into the data model

Reading and transforming the XML data into the data model using the types introduced above can be rather straightforward. Using Linq (in conjunction with XDocument/XElement), the required code is only a few lines:

public DirectoryEntry CreateDataModelFromXml(Stream xmlFileStream)
{
    XDocument xDoc = XDocument.Load(xmlFileStream);
    return new DirectoryEntry(QueryChildEntries(xDoc.Element("Folder")))
    {
        Name = "ROOT"
    };
}

private IEnumerable<NamedTreeEntry> QueryChildEntries(XElement xElem)
{
    return
        from childElem in xElem.Elements()
        where childElem.Name == "dir" || childElem.Name == "file"
        select (childElem.Name == "file") ?
            (NamedTreeEntry) new FileEntry()
                {
                    Name = childElem.Element("name").Value
                }
            : new DirectoryEntry(QueryChildEntries(childElem))
                {
                    Name = childElem.Element("name").Value,
                };
}

For the sake of readability, any kind of exception handling and sanity checks have been omitted. In your real code, these things MUST be done. The given example code might behave funny if fed with malformed/incorrect XML data.

(This example code assumes the <dir>,<file> elements being children of the <Folder> node. However, in your XML, <dir>,<file> are children of the <FolderName> element, which looks like a mistake. If this is indeed intentional, you will need to adopt the example code accordingly.)

At an appropriate place in your source code, simply call:

DirectoryEntry rootEntry;
using (FileStream stream = new FileStream(xmlFilePathString, FileMode.Open, FileAccess.Read))
{
    rootEntry = CreateDataModelFromXml(stream);
}


3. The TreeView

Now, you only need to assign the collection with the top-level entries to the TreeView control (which is called MyTreeView in this example).

MyTreeView.ItemsSource = rootEntry.ChildEntries;

If you also want to display the root entry in the tree view, do something like the following:

MyTreeView.ItemsSource = new DirectoryEntry[] { rootEntry };

In your real code, you will probably use data binding in XAML instead of setting the ItemsSource property in code-behind.

Well, with what we have done so far, the TreeView control will show only the entries in the first level. But it will not show any child entries, simply because it does not know about them. Time to introduce HierarchicalDataTemplate. HierarchicalDataTemplate not only specifies the look and feel for an entry, but it also has a parameter to bind to the collection with child entries.

For a directory entry, the HierarchicalDataTemplate could look as follows:

<HierarchicalDataTemplate DataType="{x:Type My:DirectoryEntry}" ItemsSource="{Binding ChildEntries}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Dir: " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</HierarchicalDataTemplate>

Note the DataType of the template, and the ItemsSource parameter which binds to DirectoryEntry's ChildEntries property with the collection of child entries.

For file entries a DataTemplate is sufficient, since they don't have children.

<DataTemplate DataType="{x:Type My:FileEntry}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="File: " />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</DataTemplate>

Now the TreeView control only needs to know which of these two templates is to be used for which entry. For many use cases, the TreeView control makes this very easy - just add these two templates to the TreeView control's local resource dictionary. The TreeView will select the first data template (HierarchicalDataTemplate is also a data template) from its resource dictionary whose data type matches that of the respective entry.

In summary, the complete XAML for the TreeView would look like this:

    <TreeView Name="MyTreeView">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type My:DirectoryEntry}" ItemsSource="{Binding ChildEntries}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Dir: " />
                    <TextBlock Text="{Binding Name}" />
                </StackPanel>
            </HierarchicalDataTemplate>
            <DataTemplate DataType="{x:Type My:FileEntry}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="File: " />
                    <TextBlock Text="{Binding Name}" />
                </StackPanel>
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>

(In cases, where selection of a template by data type is not feasible, an ItemTemplateSelector can be used instead.)

Sign up to request clarification or add additional context in comments.

1 Comment

Sorry, but no, the example code should be self-explanatory. If you have difficulties to understand, then your problem is not with XML or TreeView, but with C#/WPF generally.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.