Bueno, esto es probablemente un poco más ingenua de lo que en la esperanza de que, pero posiblemente podría darle un punto de partida. Podría funcionar con algunas refactorizaciones, pero fue hecho literalmente en 15 minutos, así que tómalo como lo que es, que no está bien probado o utilizando cualquier fantasía de WPF para el caso.
En primer lugar un control de usuario simple que solo alberga 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>
El código detrás de esto tendrá sólo una propiedad llamada ObjectGraph
, esto se establece en la instancia del objeto que desea examinar.
El árbol solo se carga con el primer nivel de propiedades cada nodo tiene el formato PropertyName: Value o PropertyName: Type, si la propiedad es de tipo primitivo (consulte la función IsPrimitive), se muestra el valor; de lo contrario, cadena vacía se agrega como el nodo secundario. Agregar la cadena vacía indica al usuario que el nodo puede expandirse.
Cuando se amplía el nodo, se realiza una comprobación rápida para ver si el primer hijo es una cadena vacía, si es así, el nodo se borra y las propiedades para ese nodo se cargan en el árbol.
Esto básicamente construye el árbol cuando el nodo se expande. Esto hace más fácil al igual que por dos razones
1- No hay necesidad de llevar a cabo la recursividad
2- No hay necesidad de detectar referencias cíclicas que se expandirá a la eternidad o de algún recurso que se agota, lo que ocurra primero.
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;
}
}
}
Usar el control es bastante simple. Aquí simplemente pondré el control en el Formulario y luego estableceré el ObjectGraph en una instancia de un objeto, escogí 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>
El código detrás
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;
}
}
}
Por supuesto esto todavía necesitaría mucho trabajo, un manejo especial para este tipo como matrices posiblemente un mecanismo para manejar vistas personalizadas a tipos especiales, etc.
¡No tienes idea de cuánto me has ahorrado tiempo! Sé que los comentarios no son para "Gracias", pero pasar 8 minutos para copiarlos y ajustarlos en lugar de desarrollarlos durante 80 minutos ... ¡Usted y Chris merecen un gran agradecimiento! –
@ G.Y comentarios como ese son el motivo por el que sigo tratando de responder preguntas sobre SO. Gracias ** usted ** –
Zachary - Gran trabajo. Realmente me ahorró tiempo. Para el beneficio de todos, cargué un proyecto en codeplex que se puede encontrar aquí: https://wpfobjecttreeview.codeplex.com/ –