This article is currently in the process of being translated into Spanish (~99% done).
TreeView - Selection/Expansion state
En los dos artículos anteriores de TreeView, utilizamos el enlace de datos para mostrar objetos personalizados en un WPF TreeView. Esto funciona muy bien, pero te deja con un problema: dado que cada nodo del árbol ahora está representado por tu clase personalizada, por ejemplo FamilyMember como vimos en el artículo anterior, ya no tienes control directo sobre la funcionalidad específica del nodo TreeView como la selección y estado de expansión. En la praxis, esto significa que no puede seleccionar o expandir / contraer un nodo dado desde el código subyacente.
Existen muchas soluciones para manejar esto, que van desde "hacks" donde usa los generadores de elementos de TreeView para obtener el TreeViewItem subyacente, donde puede controlar las propiedades IsExpanded e IsSelected, hasta implementaciones mucho más avanzadas inspiradas en MVVM. En este artículo me gustaría mostrarte una solución que se encuentra en algún lugar en el medio, lo que facilita su implementación y uso, sin ser un truco completo.
Una solución de selección / expansión TreeView.
El principio básico es implementar dos propiedades adicionales en su clase de datos: IsExpanded e IsSelected. Estas dos propiedades se conectan al TreeView, usando un par de estilos dirigidos al TreeViewItem, dentro del ItemContainerStyle para el TreeView.
Podría implementar fácilmente estas dos propiedades en todos sus objetos, pero es mucho más fácil heredarlas de un objeto base. Si esto no es factible para su solución, podría crear una interfaz para ella y luego implementarla en su lugar, para establecer un terreno común. Para este ejemplo, he elegido el método de clase base, porque me permite obtener fácilmente la misma funcionalidad para mis otros objetos. Aquí está el código:
<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewSelectionExpansionSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewSelectionExpansionSample" Height="200" Width="300">
<DockPanel Margin="10">
<WrapPanel Margin="0,10,0,0" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Name="btnSelectNext" Click="btnSelectNext_Click" Width="120">Select next</Button>
<Button Name="btnToggleExpansion" Click="btnToggleExpansion_Click" Width="120" Margin="10,0,0,0">Toggle expansion</Button>
</WrapPanel>
<TreeView Name="trvPersons">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" Margin="0,0,4,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Controls;
namespace WpfTutorialSamples.TreeView_control
{
public partial class TreeViewSelectionExpansionSample : Window
{
public TreeViewSelectionExpansionSample()
{
InitializeComponent();
List<Person> persons = new List<Person>();
Person person1 = new Person() { Name = "John Doe", Age = 42 };
Person person2 = new Person() { Name = "Jane Doe", Age = 39 };
Person child1 = new Person() { Name = "Sammy Doe", Age = 13 };
person1.Children.Add(child1);
person2.Children.Add(child1);
person2.Children.Add(new Person() { Name = "Jenny Moe", Age = 17 });
Person person3 = new Person() { Name = "Becky Toe", Age = 25 };
persons.Add(person1);
persons.Add(person2);
persons.Add(person3);
person2.IsExpanded = true;
person2.IsSelected = true;
trvPersons.ItemsSource = persons;
}
private void btnSelectNext_Click(object sender, RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
{
var list = (trvPersons.ItemsSource as List<Person>);
int curIndex = list.IndexOf(trvPersons.SelectedItem as Person);
if(curIndex >= 0)
curIndex++;
if(curIndex >= list.Count)
curIndex = 0;
if(curIndex >= 0)
list[curIndex].IsSelected = true;
}
}
private void btnToggleExpansion_Click(object sender, RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
(trvPersons.SelectedItem as Person).IsExpanded = !(trvPersons.SelectedItem as Person).IsExpanded;
}
}
public class Person : TreeViewItemBase
{
public Person()
{
this.Children = new ObservableCollection<Person>();
}
public string Name { get; set; }
public int Age { get; set; }
public ObservableCollection<Person> Children { get; set; }
}
public class TreeViewItemBase : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if(value != this.isSelected)
{
this.isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
if(value != this.isExpanded)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Lo siento por la gran cantidad de código en un solo lugar. En una solución del mundo real, obviamente se distribuiría en varios archivos y los datos para el árbol probablemente provengan de una fuente de datos real, en lugar de generarse sobre la marcha. Permítanme explicar lo que sucede en el ejemplo.
La parte XAML
He definido un par de botones para colocar en la parte inferior del cuadro de diálogo, para usar las dos nuevas propiedades. Luego tenemos el TreeView, para lo cual tengo definido un ItemTemplate (como se demostró en un capítulo anterior) así como un ItemContainerStyle. Si aún no has leído los capítulos sobre estilo, puede que no comprenda completamente esa parte, pero es simplemente una cuestión de vincular las propiedades en nuestra propia clase personalizada con IsSelected y IsExpanded
Parte de código subyacente
En el código subyacente, he definido una clase Persona , con un par de propiedades, que hereda nuestras propiedades adicionales de la clase TreeViewItemBase . Debe tener en cuenta que la clase TreeViewItemBase implementa la interfaz INotifyPropertyChanged y la usarlo para notificar los cambios a estas dos propiedades esenciales; sin esto, los cambios de selección / expansión no se reflejarán en la interfaz de usuario. El concepto de notificación.
En la clase de la ventana principal, simplemente creo un rango de personas, mientras agrego niños a algunas de ellas. Agrego las personas a una lista, que asigno como ItemsSource de TreeView, que, con un poco de ayuda de la plantilla definida, los muestra de la forma en que se muestran en la captura de pantalla.
La parte más interesante sucede cuando configuro las propiedades IsExpanded e IsSelected en el objeto person2 . Esto es lo que causa la segunda persona (Jane Doe) se seleccionará y expandirá inicialmente, como se muestra en la captura de pantalla. También usamos estas dos propiedades en los objetos Persona (heredados de la clase TreeViewItemBase) en los controladores de eventos para los dos botones de prueba (tenga en cuenta que, para mantener el código lo más pequeño y simple posible,
Resumen
Al crear e implementar una clase base para los objetos que desea usar y manipular dentro de un TreeView, y usar las propiedades obtenidas en ItemContainerStyle, hace que sea mucho más fácil trabajar con selecciones y estados de expansión. Hay muchas soluciones para abordar este problema, y mientras esto debería ser suficiente, es posible que pueda encontrar una solución que se adapte mejor a sus necesidades. Como siempre con la programación, se trata de usar la herramienta adecuada para el trabajo en cuestión.