2010-09-08 10 views
10

J'essaie de trouver du code ou un contrôle préemballé qui prend un graphe d'objet et affiche les propriétés publiques et les valeurs des propriétés (récursivement) dans un TreeView. Même une implémentation naïve est correcte, j'ai juste besoin de quelque chose pour commencer.Recherche d'un contrôle d'arborescence de graphique d'objet pour WPF

La solution doit être dans WPF, pas WinForms ou com, etc ...

Répondre

21

Je pris des pièces de l'exemple de Chris Taylor et la structure de a codeproject article et les ont fusionné dans ce:

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> 

Code fil-up

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

Vous n'avez aucune idée de combien vous m'avez sauvé du temps! Je sais que les commentaires ne sont pas pour "Merci" - mais passer 8 minutes pour copier et ajuster au lieu de développer pour 80 minutes ... Vous et Chris méritent un grand merci! –

+1

@ G.Y commentaires comme ça sont pourquoi je continue à essayer de répondre aux questions sur SO. Merci ** vous ** –

+1

Zachary - Excellent travail. Cela m'a vraiment fait gagner du temps. Pour le bénéfice de tous j'ai téléchargé un projet de codage qui peut être trouvé ici: https://wpfobjecttreeview.codeplex.com/ –

6

Eh bien, cela est probablement un peu plus naïf que vous où en espérant, mais il pourrait peut-être vous donner un point de départ. Cela pourrait être fait avec un refactoring, mais cela a été fait littéralement en 15 minutes alors prenez-le pour ce qu'il est, ce qui n'est pas bien testé ou en utilisant des fantaisies WPF d'ailleurs.

D'abord simple UserControl qui accueille juste un 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> 

Le code derrière pour cela aura juste une propriété appelée ObjectGraph, ce paramètre est réglé à l'instance de l'objet que vous souhaitez parcourir.

L'arborescence est uniquement chargée avec le premier niveau de propriétés chaque nœud a le format PropertyName: Value ou PropertyName: Type, si la propriété est un type primitif (voir la fonction IsPrimitive), la valeur est affichée, sinon un Une chaîne vide est ajoutée en tant que noeud enfant. L'ajout de la chaîne vide indique à l'utilisateur que le noeud peut être développé. Lorsque le noeud est expiré, une vérification rapide est effectuée pour voir si le premier enfant est une chaîne vide, si c'est le cas, le noeud est effacé et les propriétés de ce noeud sont chargées dans l'arbre. Donc, cela construit fondamentalement l'arborescence au fur et à mesure que les nœuds sont développés. Cela rend plus facile comme pour deux raisons

1- Pas besoin d'effectuer récursion

2- Pas besoin de détecter des références cycliques qui étendront à l'éternité ou une ressource appauvrie, qui vient toujours en premier.

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

L'utilisation du contrôle est assez simple. Ici, je vais juste mettre le contrôle sur Form et ensuite définir le ObjectGraph à une instance d'un objet, j'ai arbitrairement choisi 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> 

Le code derrière

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

Bien sûr, cela aurait encore besoin de beaucoup de travail, un traitement spécial pour les types comme des tableaux peut-être un mécanisme pour gérer la coutume vue à des types spéciaux etc.

+0

Great! Je vais l'essayer. –

+0

@Zachary, juste pour vous faire savoir que j'ai eu quelques minutes donc je mets rapidement dans une meilleure gestion de la propriété de dépendance. –