2010-09-08 7 views
10

Ich versuche, Code oder ein vor-gepacktes Steuerelement zu finden, die ein Objektdiagramm und zeigt die öffentlichen Eigenschaften und Werte der Eigenschaften (rekursiv) in einem TreeView. Selbst eine naive Implementierung ist in Ordnung, ich brauche nur etwas für den Anfang.Suche nach einem Objektdiagramm Tree-View-Steuerelement für WPF

Die Lösung muss in WPF, nicht Winforms oder com, etc ...

Antwort

21

Also nahm ich Teile von Chris Taylor Beispiel und die Struktur der a codeproject article und fusionierte sie in diese:

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> 

Wire-up-Code

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

Sie haben keine Ahnung, wie viel Sie mir Zeit gespart haben! Ich weiß, Kommentare sind nicht für "Danke" - aber 8 Minuten für das Kopieren und Anpassen, anstatt für 80 Minuten zu entwickeln ... Sie und Chris verdienen einen großen Dank! –

+1

@ G.Y Kommentare wie das sind der Grund, warum ich versuche, Fragen zu SO zu beantworten. Danke ** Sie ** –

+1

Zachary - Tolle Arbeit. Es hat mir wirklich Zeit gespart. Zum Vorteil von allen habe ich ein funktionierendes Projekt zu Codeplex hochgeladen, das hier gefunden werden kann: https://wpfobjecttreeview.codeplex.com/ –

6

Nun, das, als Sie wahrscheinlich ein wenig mehr naiv ist, wo gehofft, aber es könnte man möglicherweise einen Ausgangspunkt geben. Es könnte mit etwas Refactoring tun, aber es wurde buchstäblich in 15 Minuten gemacht, also nimm es für das, was es ist, was nicht gut getestet ist oder irgendwelche WPF-Phantasien für diese Angelegenheit verwendet.

Zunächst ein einfaches Benutzersteuerelement, das nur ein TreeView Gastgeber

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

Der Code hinter hierfür haben nur eine Eigenschaft ObjectGraph genannt, dies auf die Instanz des Objekts festgelegt wird, die Sie durchsuchen möchten.

Die Struktur wird nur mit der ersten Eigenschaftsebene geladen. Jeder Knoten hat das Format Eigenschaftsname: Wert oder Eigenschaftsname: Typ, wenn die Eigenschaft ein primitiver Typ ist (siehe Funktion IsPrimitiv), wird der Wert angezeigt, andernfalls ein Eine leere Zeichenfolge wird als untergeordneter Knoten hinzugefügt. Das Hinzufügen der leeren Zeichenfolge zeigt dem Benutzer an, dass der Knoten ge expandiert werden kann.

Wenn der Knoten erweitert wird, wird eine schnelle Überprüfung durchgeführt, um festzustellen, ob das erste Kind eine leere Zeichenfolge ist. Wenn dies der Fall ist, wird der Knoten gelöscht und die Eigenschaften für diesen Knoten in den Baum geladen.

Also im Grunde baut diese Baumstruktur auf, wenn der Knoten erweitert wird. Dies macht wie leichter aus zwei Gründen

1- Keine Notwendigkeit Rekursion

2- Keine Notwendigkeit zur Durchführung zyklische Referenzen zu erfassen, die in der Ewigkeit oder eine Ressource erweitern erschöpft ist, das erste jemals kommt.

Die Verwendung der Steuerung ist recht einfach. Hier werde ich nur das Steuerelement auf Form setzen und dann den ObjectGraph auf eine Instanz eines Objekts setzen, ich wählte willkürlich 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> 

Der Code hinter

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

Natürlich ist dies immer noch eine Menge Arbeit, eine spezielle Behandlung für Typen wie Arrays möglicherweise einen Mechanismus benötigen würde Ansichten zu handhaben benutzerdefinierte zu speziellen Typen usw.

+0

Great! Ich werde es ausprobieren. –

+0

@Zachary, nur um dich wissen zu lassen, dass ich ein paar Minuten Zeit habe, um die Abhängigkeitseigenschaft schnell zu verbessern. –

Verwandte Themen