寻找WPF的对象图树视图控件

我正在尝试查找代码或预先打包的控件,它采用对象图并在TreeView中显示属性的公共属性和值(递归)。 即使是天真的实现也没问题,我只需要一些东西就可以了。

解决方案必须是WPF,而不是winforms或com等…

因此,我从Chris Taylor的示例和代码项目文章的结构中获取了部分内容并将它们合并到此:

TreeView xaml:

                      

电汇代码

 void DisplayObjectGraph(object graph) { var hierarchy = new ObjectViewModelHierarchy(graph); tvObjectGraph.DataContext = hierarchy; } 

ObjectViewModel.cs:

 public class ObjectViewModel : INotifyPropertyChanged { ReadOnlyCollection _children; readonly ObjectViewModel _parent; readonly object _object; readonly PropertyInfo _info; readonly Type _type; bool _isExpanded; bool _isSelected; public ObjectViewModel(object obj) : this(obj, null, null) { } ObjectViewModel(object obj, PropertyInfo info, ObjectViewModel parent) { _object = obj; _info = info; if (_object != null) { _type = obj.GetType(); if (!IsPrintableType(_type)) { // load the _children object with an empty collection to allow the + expander to be shown _children = new ReadOnlyCollection(new ObjectViewModel[] { new ObjectViewModel(null) }); } } _parent = parent; } public void LoadChildren() { if (_object != null) { // exclude value types and strings from listing child members if (!IsPrintableType(_type)) { // the public properties of this object are its children var children = _type.GetProperties() .Where(p => !p.GetIndexParameters().Any()) // exclude indexed parameters for now .Select(p => new ObjectViewModel(p.GetValue(_object, null), p, this)) .ToList(); // if this is a collection type, add the contained items to the children var collection = _object as IEnumerable; if (collection != null) { foreach (var item in collection) { children.Add(new ObjectViewModel(item, null, this)); // todo: add something to view the index value } } _children = new ReadOnlyCollection(children); this.OnPropertyChanged("Children"); } } } ///  /// Gets a value indicating if the object graph can display this type without enumerating its children ///  static bool IsPrintableType(Type type) { return type != null && ( type.IsPrimitive || type.IsAssignableFrom(typeof(string)) || type.IsEnum); } public ObjectViewModel Parent { get { return _parent; } } public PropertyInfo Info { get { return _info; } } public ReadOnlyCollection Children { get { return _children; } } public string Type { get { var type = string.Empty; if (_object != null) { type = string.Format("({0})", _type.Name); } else { if (_info != null) { type = string.Format("({0})", _info.PropertyType.Name); } } return type; } } public string Name { get { var name = string.Empty; if (_info != null) { name = _info.Name; } return name; } } public string Value { get { var value = string.Empty; if (_object != null) { if (IsPrintableType(_type)) { value = _object.ToString(); } } else { value = ""; } return value; } } #region Presentation Members public bool IsExpanded { get { return _isExpanded; } set { if (_isExpanded != value) { _isExpanded = value; if (_isExpanded) { LoadChildren(); } this.OnPropertyChanged("IsExpanded"); } // Expand all the way up to the root. if (_isExpanded && _parent != null) { _parent.IsExpanded = true; } } } public bool IsSelected { get { return _isSelected; } set { if (_isSelected != value) { _isSelected = value; this.OnPropertyChanged("IsSelected"); } } } public bool NameContains(string text) { if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(Name)) { return false; } return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1; } public bool ValueContains(string text) { if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(Value)) { return false; } return Value.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1; } #endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } 

ObjectViewModelHierarchy.cs:

 public class ObjectViewModelHierarchy { readonly ReadOnlyCollection _firstGeneration; readonly ObjectViewModel _rootObject; public ObjectViewModelHierarchy(object rootObject) { _rootObject = new ObjectViewModel(rootObject); _firstGeneration = new ReadOnlyCollection(new ObjectViewModel[] { _rootObject }); } public ReadOnlyCollection FirstGeneration { get { return _firstGeneration; } } } 

嗯,这可能比你想要的更天真,但它可能会给你一个起点。 它可以用一些重构,但它确实在15分钟内完成,所以把它当作它,它没有经过充分测试或使用任何WPF幻想。

首先是一个简单的UserControl,它只承载一个TreeView

      

这背后的代码只有一个名为ObjectGraph属性,它被设置为您要浏览的对象的实例。

树只加载第一级属性,每个节点具有格式PropertyName:Value或PropertyName:Type,如果属性是基本类型(请参阅IsPrimitive函数),则显示该值,否则添加空字符串作为子节点。 添加空字符串向用户指示节点可以扩展。

当节点被扩展时,进行快速检查以查看第一个子节点是否为空字符串,如果是,则清除节点并将该节点的属性加载到树中。

因此,当节点扩展时,这基本上构建了树。 这有两个原因使得更容易

1-无需执行递归

2-无需检测循环参考,这些循环参考将扩展到永恒或某些资源耗尽,这是先到先得的。

 using System; using System.Windows; using System.Windows.Controls; using System.Reflection; namespace ObjectBrowser { public partial class PropertyTree : UserControl { public PropertyTree() { InitializeComponent(); } private void treeView1_Expanded(object sender, RoutedEventArgs e) { TreeViewItem item = e.OriginalSource as TreeViewItem; if (item.Items.Count == 1 && item.Items[0].ToString() == string.Empty) { LoadGraph(item.Items, item.Tag); } } public object ObjectGraph { get { return (object)GetValue(ObjectGraphProperty); } set { SetValue(ObjectGraphProperty, value); } } public static readonly DependencyProperty ObjectGraphProperty = DependencyProperty.Register("ObjectGraph", typeof(object), typeof(PropertyTree), new UIPropertyMetadata(0, OnObjectGraphPropertyChanged)); private static void OnObjectGraphPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { PropertyTree control = source as PropertyTree; if (control != null) { control.OnObjectGraphChanged(source, EventArgs.Empty); } } protected virtual void OnObjectGraphChanged(object sender, EventArgs e) { LoadGraph(treeView1.Items, ObjectGraph); } private void LoadGraph(ItemCollection nodeItems, object instance) { nodeItems.Clear(); if (instance == null) return; Type instanceType = instance.GetType(); foreach (PropertyInfo pi in instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { object propertyValue =pi.GetValue(instance, null); TreeViewItem item = new TreeViewItem(); item.Header = BuildItemText(instance, pi, propertyValue); if (!IsPrimitive(pi) && propertyValue != null) { item.Items.Add(string.Empty); item.Tag = propertyValue; } nodeItems.Add(item); } } private string BuildItemText(object instance, PropertyInfo pi, object value) { string s = string.Empty; if (value == null) { s = ""; } else if (IsPrimitive(pi)) { s = value.ToString(); } else { s = pi.PropertyType.Name; } return pi.Name + " : " + s; } private bool IsPrimitive(PropertyInfo pi) { return pi.PropertyType.IsPrimitive || typeof(string) == pi.PropertyType; } } } 

使用控件非常简单。 这里我将把控件放在Form上,然后将ObjectGraph设置为一个对象的实例,我随意选择了XmlDataProvider

XAML

      

背后的代码

 using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace ObjectBrowser { ///  /// Interaction logic for MainWindow.xaml ///  public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { var o = new XmlDataProvider(); o.Source = new Uri("http://www.stackoverflow.com"); propertyTree1.ObjectGraph = o; } } } 

当然,这仍然需要大量的工作,对数组等类型的特殊处理可能是处理特殊类型的自定义视图的机制等。