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

时间:2010-09-08 14:33:43

标签: c# reflection treeview wpf-controls

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

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

2 个答案:

答案 0 :(得分:23)

所以我从Chris Taylor的例子和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; }
    }
}

答案 1 :(得分:6)

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

首先是一个简单的UserControl,它只托管一个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函数),则显示该值,否则为空string被添加为子节点。添加空字符串向用户指示节点可以扩展。

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

因此,当节点扩展时,这基本上构建了树。由于两个原因,这样做更容易

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

使用控件非常简单。在这里,我将控件放在Form上,然后将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;
    }
  }
}

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