如何将文件夹结构从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;
}
}
答案 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代替。)