2010-09-08 57 views
10

我在查找代码或预先打包的控件,它需要一个对象图并在TreeView中显示公共属性和属性值(递归)。即使是一个天真的执行是好的,我只需要一些开始。寻找WPF的对象图树形视图控件

解决方案必须在WPF中,没有的WinForms或COM等..

回答

21

所以我从克里斯·泰勒的例子零部件和a codeproject article结构和它们合并成这样:

TreeView控件XAML:

<TreeView Name="tvObjectGraph" ItemsSource="{Binding FirstGeneration}" Margin="12,41,12,12" FontSize="13" FontFamily="Consolas"> 
    <TreeView.ItemContainerStyle> 
     <Style TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> 
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
      <Setter Property="FontWeight" Value="Normal" /> 
      <Style.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter Property="FontWeight" Value="Bold" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </TreeView.ItemContainerStyle> 
    <TreeView.ItemTemplate> 
     <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 
      <Grid> 
       <Grid.ColumnDefinitions> 
        <ColumnDefinition /> 
        <ColumnDefinition /> 
        <ColumnDefinition /> 
       </Grid.ColumnDefinitions> 
       <Grid.RowDefinitions> 
        <RowDefinition /> 
       </Grid.RowDefinitions> 
       <TextBlock Text="{Binding Name}" Grid.Column="0" Grid.Row="0" Padding="2,0" /> 
       <TextBlock Text="{Binding Type}" Grid.Column="1" Grid.Row="0" Padding="2,0" /> 
       <TextBlock Text="{Binding Value}" Grid.Column="2" Grid.Row="0" Padding="2,0" /> 
      </Grid> 
     </HierarchicalDataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 

电线上码

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

ObjectViewModel.cs:

public class ObjectViewModel : INotifyPropertyChanged 
{ 
    ReadOnlyCollection<ObjectViewModel> _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<ObjectViewModel>(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<ObjectViewModel>(children); 
       this.OnPropertyChanged("Children"); 
      } 
     } 
    } 

    /// <summary> 
    /// Gets a value indicating if the object graph can display this type without enumerating its children 
    /// </summary> 
    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<ObjectViewModel> 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 = "<null>"; 
      } 
      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<ObjectViewModel> _firstGeneration; 
    readonly ObjectViewModel _rootObject; 

    public ObjectViewModelHierarchy(object rootObject) 
    { 
     _rootObject = new ObjectViewModel(rootObject); 
     _firstGeneration = new ReadOnlyCollection<ObjectViewModel>(new ObjectViewModel[] { _rootObject }); 
    } 

    public ReadOnlyCollection<ObjectViewModel> FirstGeneration 
    { 
     get { return _firstGeneration; } 
    } 
} 
+5

你不知道你为我节省了多少时间!我知道评论不是“谢谢” - 但花8分钟进行复制和调整,而不是开发80分钟......你和克里斯值得非常感谢! – 2014-03-26 21:25:46

+1

@ G.Y这样的评论就是为什么我一直试图回答关于SO的问题。感谢**你** – 2014-03-26 21:57:45

+1

Zachary - 出色的工作。这真的节省了我的时间。为了所有我的利益,我上传了一个工作项目到codeplex可以在这里找到:https://wpfobjecttreeview.codeplex.com/ – 2014-06-26 07:10:51

6

嗯,这可能是比你多了几分天真其中希望,但它可能给你一个起点。它可以做一些重构,但它在15分钟内就完成了,所以就拿它来说,它没有经过很好的测试,或者对于这个问题使用任何WPF幻想。

首先一个简单的用户控件刚刚举办一个TreeView

<UserControl x:Class="ObjectBrowser.PropertyTree" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <Grid> 
    <TreeView Name="treeView1" TreeViewItem.Expanded="treeView1_Expanded" /> 
    </Grid> 
</UserControl> 

的代码背后,这将有一个属性叫做ObjectGraph,这是设置为您要浏览的对象的实例。

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

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

因此,这基本上建立了树随着节点扩展。这使得更容易一样,原因有两个

无需执行递归

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 = "<null>"; 
     } 
     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; 
    }  
    } 
} 

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

XAML

<Window x:Class="ObjectBrowser.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:ObjectBrowser" Loaded="Window_Loaded"> 
    <Grid> 
    <my:PropertyTree x:Name="propertyTree1" /> 
    </Grid> 
</Window> 

背后

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 

namespace ObjectBrowser 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    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; 
    } 
    } 
} 

当然,这仍然需要大量的工作,特殊处理类型,如数组可能是一个自定义处理意见机制的代码特殊类型等

+0

太好了!我会尝试一下。 – 2010-09-08 17:12:01

+0

@Zachary,只是为了让你知道我有几分钟的时间,所以我很快就更好地处理了依赖属性。 – 2010-09-08 17:31:16