2010-09-08 22 views

risposta

21

Allora presi le parti da l'esempio di Chris Taylor e la struttura di a codeproject article e li fuse in questo:

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

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

Non hai idea di quanto mi hai salvato il tempo! So che i commenti non sono per "Grazie", ma spendendo 8 minuti per copiare e regolare invece di sviluppare per 80 minuti ... Tu e Chris meritate un grande ringraziamento! –

+1

@ G.Y commenti del genere sono il motivo per cui continuo a provare a rispondere alle domande su SO. Grazie ** you ** –

+1

Zachary - Ottimo lavoro. Mi ha davvero salvato tempo. Per il beneficio di tutti ho caricato un progetto di lavoro in codeplex che può essere trovato qui: https://wpfobjecttreeview.codeplex.com/ –

6

Bene, questo è probabilmente un po 'più ingenuo di quanto si dove sperando, ma potrebbe forse dare un punto di partenza. Potrebbe fare con alcuni refactoring, ma è stato fatto letteralmente in 15 minuti, quindi prendilo per quello che è, che non è ben testato o usa qualsiasi fantasia WPF per quella materia.

Prima un semplice controllo utente che ospita solo 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> 

Il codice dietro per questo avrà solo una proprietà chiamata ObjectGraph, questo è impostato all'istanza dell'oggetto che si desidera navigare.

L'albero viene caricato solo con il primo livello di proprietà ogni nodo ha il formato PropertyName: Value o PropertyName: Type, se la proprietà è un tipo primitivo (vedere la funzione IsPrimitive), viene visualizzato il valore, altrimenti un la stringa vuota viene aggiunta come nodo figlio. L'aggiunta della stringa vuota indica all'utente che il nodo può essere espanso.

Quando il nodo viene aperto, viene eseguito un controllo rapido per verificare se il primo figlio è una stringa vuota, se è quindi il nodo viene cancellato e le proprietà per quel nodo vengono caricate nell'albero.

Quindi questo fondamentalmente genera l'albero come il nodo viene espanso. Questo rende più facile come per due motivi

1- Non c'è bisogno di eseguire la ricorsione

2- Non c'è bisogno di rilevare i riferimenti ciclici, che si espanderà per l'eternità o qualche risorsa è esaurito, che mai viene prima.

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'utilizzo del controllo è abbastanza semplice. Qui metto il controllo su Form e poi l'ObjectGraph su un'istanza di un oggetto, ho scelto arbitrariamente 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> 

Il codice dietro

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

Naturalmente questo sarebbe ancora bisogno di un sacco di lavoro, la gestione speciale per i tipi come gli array, eventualmente, un meccanismo per gestire visualizzazioni personalizzate a tipi speciali ecc.

+0

Great! Ci proverò. –

+0

@Zachary, solo per farti sapere che ho qualche minuto così ho rapidamente messo meglio a gestire la proprietà di dipendenza. –

Problemi correlati