2010-09-08 29 views
10

Tôi đang cố gắng tìm mã hoặc điều khiển được đóng gói sẵn có biểu đồ đối tượng và hiển thị các thuộc tính công cộng và giá trị của thuộc tính (đệ quy) trong TreeView. Ngay cả một thực hiện ngây thơ là ok, tôi chỉ cần một cái gì đó để bắt đầu.Tìm kiếm điều khiển dạng cây đồ thị đối tượng cho WPF

Các giải pháp phải nằm trong WPF, không winforms hoặc com, vv ...

Trả lời

21

Vì vậy, tôi mất phần từ ví dụ Chris Taylor và cấu trúc của a codeproject article và sáp nhập chúng vào này:

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

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

Bạn không biết mình tiết kiệm được bao nhiêu thời gian! Tôi biết ý kiến ​​không phải là "Cảm ơn" - nhưng dành 8 phút để sao chép và điều chỉnh thay vì phát triển trong 80 phút ... Bạn và Chris xứng đáng nhận được lời cảm ơn lớn! –

+1

@ G.Y nhận xét như vậy là lý do tại sao tôi tiếp tục cố gắng trả lời các câu hỏi về SO. Cảm ơn ** bạn ** –

+1

Zachary - Great work. Nó thực sự giúp tôi tiết kiệm thời gian. Vì lợi ích của tất cả tôi đã tải lên một dự án làm việc để codeplex có thể được tìm thấy ở đây: https://wpfobjecttreeview.codeplex.com/ –

6

Vâng, điều này có lẽ ngây thơ hơn một chút so với bạn nơi mong đợi, nhưng nó có thể có thể cung cấp cho bạn một điểm khởi đầu. Nó có thể làm với một số tái cấu trúc, nhưng nó đã được thực hiện theo nghĩa đen trong 15 phút để mang nó cho những gì nó được, mà không được kiểm tra tốt hoặc sử dụng bất kỳ fancies WPF cho rằng vấn đề.

Lần đầu tiên một UserControl đơn giản mà chỉ tổ chức một 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> 

Các mã sau này sẽ chỉ có một tính chất gọi là ObjectGraph, điều này được thiết lập để thể hiện của đối tượng bạn muốn duyệt.

Cây chỉ được tải với cấp thuộc tính đầu tiên mỗi nút có định dạng PropertyName: Value hoặc PropertyName: Loại, nếu thuộc tính là kiểu nguyên thủy (xem hàm IsPrimitive), thì giá trị được hiển thị, nếu không thì chuỗi rỗng được thêm vào dưới dạng nút con. Việc thêm chuỗi trống cho biết người dùng có thể mở rộng nút.

Khi nút được thoát ra, kiểm tra nhanh được thực hiện để xem liệu đứa con đầu tiên có phải là một chuỗi rỗng hay không, nếu nút đó bị xóa và thuộc tính cho nút đó được tải vào cây.

Vì vậy, điều này về cơ bản xây dựng cây lên khi nút được mở rộng. Điều này làm như dễ dàng hơn vì hai lý do

1- Không cần phải thực hiện đệ quy

2- Không cần phải phát hiện tài liệu tham khảo theo chu kỳ đó sẽ mở rộng để vĩnh cửu hoặc một số tài nguyên bị cạn kiệt, trong đó bao giờ xảy ra trước.

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

Sử dụng điều khiển khá đơn giản. Ở đây tôi sẽ chỉ đặt điều khiển trên Form và sau đó đặt ObjectGraph thành một thể hiện của một đối tượng, tôi tự ý chọn 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> 

Mã đằng sau

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

Tất nhiên điều này vẫn sẽ cần rất nhiều công việc, xử lý đặc biệt với nhiều loại như mảng có thể là một cơ chế để xử lý quan điểm tùy chỉnh đến các loại đặc biệt, v.v.

+0

Tuyệt vời! Tôi se thử no. –

+0

@Zachary, chỉ để cho bạn biết tôi có vài phút để tôi nhanh chóng xử lý tốt hơn thuộc tính phụ thuộc. –

Các vấn đề liên quan