Ищем объект граф дерева управления для WPF - PullRequest
11 голосов
/ 08 сентября 2010

Я пытаюсь найти код или предварительно упакованный элемент управления, который берет граф объекта и отображает общедоступные свойства и значения свойств (рекурсивно) в TreeView. Даже наивная реализация в порядке, мне просто нужно с чего-то начать.

Решение должно быть в WPF, а не в winforms или com, и т.д ...

Ответы [ 2 ]

23 голосов
/ 09 сентября 2010

Итак, я взял части из примера Криса Тейлора и структуры статьи кода проекта и объединил их в следующее:

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; }
    }
}
6 голосов
/ 08 сентября 2010

Ну, это, вероятно, немного более наивно, чем вы надеялись, но это может дать вам отправную точку.Это можно было бы сделать с некоторым рефакторингом, но буквально это было сделано за 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), то значение отображается,в противном случае пустая строка добавляется в качестве дочернего узла.Добавление пустой строки указывает пользователю, что узел может быть расширен.

Когда узел расширяется, выполняется быстрая проверка, чтобы увидеть, является ли первый дочерний элемент пустой строкой, если это так, то узел очищается и свойства этого узла загружаются в дерево.

Таким образом, это в основном строит дерево по мере расширения узла.Это упрощает процесс по двум причинам:

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

Использовать элемент управления довольно просто.Здесь я просто поместил элемент управления в форму, а затем установил для 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;
    }
  }
}

Конечно, это все еще потребует большой работы, специальной обработки для типов, таких как массивы, возможно, механизма для обработки пользовательских представлений для специальных типов и т. Д.

...