2010-09-08 10 views

Odpowiedz

21

Więc wziąłem części z przykładu Chrisa Taylora i struktury a codeproject article i połączyła je w ten sposób:

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 kod

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

Nie masz pojęcia, ile mnie uratowałeś! Wiem, że komentarze nie są dla "Dziękuję" - ale spędzają 8 minut na kopiowaniu i dostosowywaniu zamiast rozwijaniu przez 80 minut ... Ty i Chris zasługują na wielkie podziękowania! –

+1

@ G.Y Takie komentarze powodują, że staram się odpowiadać na pytania dotyczące SO. Dziękuję ** Ty ** –

+1

Zachary - Świetna robota. Naprawdę zaoszczędził mi czasu. Z myślą o wszystkich przesłałem działający projekt do codeplexu, który można znaleźć tutaj: https://wpfobjecttreeview.codeplex.com/ –

6

Cóż, to chyba trochę bardziej naiwny niż ty gdzie nadzieję, ale mogłaby dać punkt wyjścia. To może zrobić z pewnym refaktoryzacji, ale zostało to zrobione dosłownie w ciągu 15 minut, więc weź to za to, co jest, co nie jest dobrze przetestowane ani nie używa żadnych wyobrażeń o WPF.

pierwsze proste UserControl, który właśnie odbywa 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> 

związany kod za to będzie miał tylko jedną właściwość o nazwie ObjectGraph, to jest ustawione na wystąpienie obiektu, który chcesz przeglądać.

Drzewo zostaje załadowane tylko z pierwszym poziomem właściwości dla każdego węzła ma format Nazwa właściwości: Wartość lub Nazwa właściwości: Typ, jeśli właściwość jest typem pierwotnym (patrz funkcja IsPrimitive), wówczas wyświetlana jest wartość, w przeciwnym razie pusty łańcuch jest dodawany jako węzeł potomny. Dodanie pustego łańcucha wskazuje użytkownikowi, że węzeł może się rozwinąć.

Po wypuszczeniu węzła następuje szybkie sprawdzenie, czy pierwsze dziecko jest pustym łańcuchem, czy jest to węzeł jest wyczyszczony, a właściwości dla tego węzła są załadowane do drzewa.

To zasadniczo buduje drzewo w miarę powiększania się węzła. To sprawia, że ​​łatwiej jakby z dwóch powodów

1- Nie trzeba wykonywać rekursji

2- Nie ma potrzeby, aby wykryć cykliczne odniesienia, które rozwinie się wiecznością lub jakiegoś zasobu jest wyczerpany, co nastąpi pierwsze.

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

Korzystanie z kontroli jest dość proste. Tutaj po prostu ustawię formant, a następnie ustawię ObjectGraph na instancję obiektu, arbitralnie wybrałem 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> 

Kod za

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

Oczywiście byłoby to jeszcze trzeba wiele pracy, specjalnej obsługi dla typów, takich jak macierze ewentualnie mechanizm obsługiwać niestandardowe widoki do specjalnych typów itp.

+0

Great! Wypróbuję to. –

+0

@Zachary, aby poinformować, że mam kilka minut, więc szybko wprowadzam lepszą obsługę właściwości zależności. –

Powiązane problemy