如何从xml文件到wpf treeview显示文件夹结构?

时间:2013-11-14 22:22:35

标签: c# xml wpf linq treeview

如何将文件夹结构从xml文件显示到wpf treeview?我已经尝试了Josh Smith的文章http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewModel的第二部分(Regions),但是,如何扫描或显示包含未知级别的文件夹结构的xml文件?

下面是我要在wpf treeview中显示的xml文件。将名称作为属性而不是元素是更好的做法吗?

<?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。 以下是我不成功的尝试。我想将xml文件内容保存到List中,以便使用mvvm模式绑定到我的wpf treeview。

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;
                }            
        }

1 个答案:

答案 0 :(得分:3)

要在TreeView中显示XML文件中的数据,数据将在数据模型中进行组织,这样可以轻松地通过TreeView进行操作和表示。


1。数据模型

树中的每个条目都来自一个公共基类型。

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

由于所有条目都必须显示名称,因此在基本类型中也会声明适当的属性 DisplayName

对于我们的情况,必须考虑从该基类型派生的两种具体类型:目录和文件。

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
}

请注意子列表使用 ObservableCollection<T> 类型。 尽管对于此处概述的示例不是严格必需的,但ObservableCollection&lt; T&gt;允许动态添加或删除子条目,同时TreeView控件自动保持最新。


2。将XML转换为数据模型

使用上面介绍的类型读取XML数据并将其转换为数据模型可能相当简单。使用Linq(与XDocument / XElement一起使用),所需的代码只有几行:

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,
                };
}

为了便于阅读,省略了任何类型的异常处理和健全性检查。在您的真实代码中,必须完成这些事情。如果输入格式错误/不正确的XML数据,给定的示例代码可能会很有趣。

(此示例代码假定&lt; dir&gt;,&lt; file&gt;元素是&lt; Folder&gt;节点的子元素。但是,在您的XML中,&lt; dir&gt;,&lt; file&gt;是&lt; FolderName&gt;元素,看起来像是一个错误。如果这确实是故意的,你需要相应地采用示例代码。)

在源代码中的适当位置,只需致电:

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


第3。 TreeView

现在,您只需要将带有顶级条目的集合分配给TreeView控件(在此示例中称为 MyTreeView )。

MyTreeView.ItemsSource = rootEntry.ChildEntries;

如果您还想在树视图中显示根条目,请执行以下操作:

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

在您的实际代码中,您可能会在XAML中使用数据绑定,而不是在代码隐藏中设置 ItemsSource 属性。

好吧,到目前为止我们所做的工作,TreeView控件将只显示第一级的条目。但它不会显示任何子条目,只是因为它不知道它们。是时候介绍 HierarchicalDataTemplate 了。 HierarchicalDataTemplate不仅指定条目的外观,还包含一个参数,用于绑定带有子条目的集合。

对于目录条目,HierarchicalDataTemplate可能如下所示:

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

请注意模板的 DataType ,以及 ItemsSource 参数,该参数使用子条目集合绑定到DirectoryEntry的 ChildEntries 属性。

对于文件条目, DataTemplate 就足够了,因为它们没有子项。

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

现在TreeView控件只需要知道这两个模板中的哪一个用于哪个条目。 对于许多用例,TreeView控件使这非常简单 - 只需将这两个模板添加到TreeView控件的本地资源字典中即可。 TreeView将从其资源字典中选择第一个数据模板(HierarchicalDataTemplate也是一个数据模板),其资源类型与相应条目的数据类型相匹配。

总之,TreeView的完整XAML如下所示:

    <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>

(如果根据数据类型选择模板不可行,则可以使用ItemTemplateSelector代替。)