This article is currently in the process of being translated into Slovak (~96% done).
TreeView - Selection/Expansion state
V predošlých niekoľkých článkoch TreeView sme použili dátové viazanie na zobrazenie vlastných objektov v WPF TreeView. Funguje to naozaj dobre, ale necháva vás to jeden problém: Pretože každý uzol stromu je teraz reprezentovaný vašou vlastnou triedou, napríklad FamilyMember, ako sme videli v predchádzajúcom článku, už nemáte priamu kontrolu nad špecifickou funkciou uzla TreeView, ako je napríklad výber a stav rozšírenia. V praxi to znamená, že nemôžete vybrať alebo rozbaliť / zbaliť daný uzol z dôvodu nenaprogramovania.
Na vyriešenie tohto problému existuje veľa riešení, od „hackov“, kde pomocou generátorov položiek TreeView získate základné TreeViewItem, kde môžete ovládať vlastnosti IsExpanded a IsSelected, až po pokročilejšie implementácie inšpirované MVVM. V tomto článku by som vám chcel ukázať riešenie, ktoré leží niekde v strede, čo uľahčuje implementáciu a používanie, pričom stále nejde o úplný hack.
A TreeView selection/expansion solution
Základným princípom je implementácia dvoch ďalších vlastností do vašej triedy údajov: IsExpanded a IsSelected. Tieto dve vlastnosti sa potom spoja s TreeView pomocou niekoľkých štýlov zacielených na TreeViewItem vo vnútri ItemContainerStyle do TreeView.
Tieto dve vlastnosti by ste mohli ľahko implementovať do všetkých svojich objektov, ale oveľa jednoduchšie je zdediť ich od základného objektu. Ak to nie je možné pre vaše riešenie, môžete preň vytvoriť rozhranie a potom ho implementovať, aby ste vytvorili spoločnú reč. Pre tento príklad som si vybral metódu základnej triedy, pretože mi to umožňuje veľmi ľahko získať rovnakú funkcionalitu pre moje ďalšie objekty. Tu je kód:
<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));
}
}
}
Ospravedlňujem sa za dosť veľké množstvo kódu na jednom mieste. V riešení v reálnom svete by sa to samozrejme rozložilo na viac súborov namiesto toho a údaje pre strom by pravdepodobne pochádzali zo skutočného zdroja údajov, namiesto toho, aby boli generované za behu. Dovoľte mi vysvetliť, čo sa stane v príklade.
XAML part
Definoval som niekoľko tlačidiel, ktoré sa umiestnia do dolnej časti dialógového okna, aby som použil tieto dve nové vlastnosti. Potom máme TreeView, pre ktorý som definoval ItemTemplate (ako je uvedené v predchádzajúcej kapitole), ako aj ItemContainerStyle. Ak ste ešte nečítali kapitoly o stylingu, nemusíte túto časť úplne pochopiť, ale je to jednoducho otázka prepojenia vlastností našej vlastnej triedy pomocou IsSelected a IsExpanded na TreeViewItems, čo sa vykonáva pomocou nastavovačov štýlov. Viac sa o nich môžete dozvedieť inde v tomto návode.
Code-behind part
V pozadí s kódom som definoval triedu Osoba s niekoľkými vlastnosťami, ktoré zdedia naše ďalšie vlastnosti z triedy TreeViewItemBase . Mali by ste si byť vedomí, že trieda TreeViewItemBase implementuje rozhranie INotifyPropertyChanged a používa ho na upozornenie na zmeny týchto dvoch základných vlastností - bez tohto sa zmeny výberu / rozšírenia nebudú odrážať v používateľskom rozhraní. Pojem zmeny oznámení je vysvetlený v kapitolách o záväzných údajoch.
V main Window som jednoducho vytvoril okruh osôb, pričom k niektorým z nich som pridal deti. Pridám osoby do zoznamu, ktorý priraďujem ako ItemSource TreeView, ktorý s trochou pomoci z definovanej šablóny ich vykreslí tak, ako sú zobrazené na snímke obrazovky.
Najzaujímavejšia časť sa stane, keď nastavím vlastnosti IsExpanded a IsSelected na objekt person2 . To spôsobuje, že druhá osoba (Jane Doe) bola pôvodne vybraná a rozšírená, ako je to zobrazené na snímke obrazovky. Tieto dve vlastnosti používame aj na objektoch Person (zdedené z triedy TreeViewItemBase) v obsluhe udalostí pre dve testovacie tlačidlá (nezabudnite, že aby bol kód čo najmenší a najjednoduchší, tlačidlo výberu funguje iba pre položky najvyššej úrovne).
Summary
Vytvorením a implementáciou základnej triedy pre objekty, ktoré chcete používať a manipulovať v rámci TreeView, a pomocou získaných vlastností v ItemContainerStyle, je oveľa jednoduchšie pracovať s výbermi a stavmi rozšírenia. Existuje veľa riešení, ako tento problém vyriešiť. Aj keď by to malo stačiť, môžete nájsť riešenie, ktoré lepšie vyhovuje vašim potrebám. Ako vždy pri programovaní, je to všetko o použití správneho nástroja pre danú úlohu.