WPF (Windows Presentation Foundation) è un framework davvero potente (oltreché pesante) e permette di creare componenti grafici dalle caratteristiche avanzate con pochissimo sforzo! Ma se volessimo estendere qualcosa di già esistente e non creare nulla da zero?

mytreenode
(Nonostante non si noti, quest’albero è in WPF)

Intanto perché WPF? Perché Vista ed i suoi successori lo integrano attraverso il .Net Framework 3.5, perché con poco sforzo ci permette interfacce grafiche futuristiche ma, soprattutto, perché me lo dice il mio prof!😛

Il concetto che sta dietro WPF è molto semplice: tutto può contenere tutto! Quindi possiamo creare una ComboBox con all’interno, invece delle solite stringhe, una serie di video con una descrizione accanto (stile youtube), e poi magari inserire questa ComboBox come nodo in un albero di preferenze! Pensate di riuscire a farlo in altri linguaggi di programmazione? Solo Flash permette, con un notevole sforzo in ActionScript, di arrivare a tanto, ma poi si è ostacolati dal limite di non poter interfacciarsi con l’esterno!

Il piccolo anello mancante però è: e se io invece volessi qualcosa di semplice? Tipo, il vecchio albero in Windows Form permetteva l’uso delle CheckBox, come potrei fare a creare una mia classe che estenda l’albero di WPF con questa funzionalità? Leggendo su internet o sui libri l’unica alternativa sembra crearsi un UserControl, un HeaderedItemsControl, un ItemsControl e chi più ne ha più ne metta!

Invece, utilizzando in coppia XAML e C#, il risultato è facilmente ottenibile! Come prima cosa andiamo ad aggiungere al progetto un nuovo elemento UserControl e, chiamiamolo, ad esempio, MyTreeNode. Questo inserirà nel progetto gli elementi MyTreeNode.xaml e MyTreeNode.xaml.cs: il primo è l’interfaccia scritta in XAML, il secondo è il codice sottostante, scritto in C#.

Partiamo con il modificare l’interfaccia in XAML, che è più leggibile al programmatore e meno criptica (una volta giunti alla soluzione)! Vi posto direttamente la soluzione finale:

<TreeViewItem x:Class=”WPFApplication.MyTreeNode”

xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;

xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;

xmlns:my=”clr-namespace:WPFApplication”

>

<TreeViewItem.Header>

<StackPanel Orientation=”Horizontal”>

<CheckBox

x:Name=”UICheckBox”

IsChecked=”{Binding Checked}”

Visibility=”{Binding TreeView.CheckBoxes}” />

<ContentControl

x:Name=”UITitle”

Content=”{Binding Title}” />

</StackPanel>

</TreeViewItem.Header>

</TreeViewItem>

Come potete vedere ho sostituito UserControl con TreeViewItem (l’elemento nodo in WPF) e successivamente ho ridefinito l’Header inserendoci al suo posto uno pannello con una CheckBox ed un Titolo (in qualche modo bisogna accedere all’intestazione, considerato che ora Header non è più disponibile. Per i curiosi Title sarebbe l’equivalente di Text nelle Windows Form).
Il Binding utilizzato per Visibility serve per creare la stessa identica struttura delle Windows Form e si basa su un’Attached Property nel codice C# che cambia al cambiare dell’elemento grafico padre, ma questa è un’altra storia! L’ho lasciato per completezza! ^_^

Passiamo ora ad analizzare porzioni di codice C# (scrivere tutta la classe sarebbe spropositato!), intanto come prima cosa modifichiamo la classe da cui ereditare tutto, che non sarà più UserControl ma TreeViewItem:

public partial class MyTreeNode : TreeViewItem

Poi andiamo a modificare il costruttore inserendo queste linee di codice:

UICheckBox.DataContext = this;
UITitle.DataContext = this;

Quello che faccio è definire la sorgente per i vari binding , farlo da XAML è molto meno immediato e facilmente soggetto a malfunzionamenti!

Considerato che l’Header è stato sostituito dal pannello rendiamolo di sola lettura:

public new object Header

{
get { return base.Header; }
//set { Title = value; }
}

Come vedete dal commento è possibile anche prevedere che tutto quello che viene inserito nell’Header passi direttamente nel Title, avendo così un comportamento molto simile sia a WPF che a Windows Form. Nonostante sia comodo (e allettante) come approccio questo implicherebbe avere in lettura oggetti diversi da quelli appena scritti, cosa che potrebbe dare inutili grattacapi a programmatori che in futuro volessero utilizzare il vostro codice! Quindi consiglio di lasciare la sola lettura all’Header!

Ora non rimane che crearci le Dependency Property appropriate:

  • Title: questa DP andrà a sostuiture l’Header, quindi deve essere di tipo object. Per un utilizzo vicino alle Windows Form si può prevedere di inserire un CoerceValueCallback che in caso di elementi non grafici converti in stringa l’oggetto (WPF ha un suo casting particolare, quindi è bene non affidarsi troppo a questo!), il controllo da inserire è davvero semplice:

    object result = value is UIElement ? value : value.ToString();
    return result;

  • Checked: questa DP indica se una CheckBox è selezionata o meno, quindi è di tipo booleano.
  • TreeView.CheckBoxes: è collegata alla DP TreeView (un’altra classe dedicata non trattata in questo articolo) ed è di tipo Visibility. TreeView (di tipo MyTreeView, per rispettare la convenzione utilizzata) cambia al cambiare dell’elemento grafico padre, notificando tutti i nodi figli ed aggiornando la visibilità delle CheckBox. Essendo una classe lunga evito di inserirla in questo articolo che si focalizza su altri aspetti.

Bene, direi che con poco sforzo abbiamo ottenuto il vecchio comportamento delle Windows Form e ci siamo lasciati lo spazio per inserire facilmente altre funzionalità nell’Header!🙂

PS: nel caso ve lo chiediate, ricordatevi che i Nodes di Windows Form sono gli Items di WPF, il metodo EnsureVisible() diventa BringIntoView(), l’elemento TreeView non esiste e dovete crearlo voi, gli eventi sono stati sostituiti dalle DP, non esiste il LabelEdit, i menu contestuali sono cambiati (anche se son facili da adattare), se non erro il Drag & Drop è cambiato ma comunque è compatibile con Windows Form, non si pone più il problema dei refresh, ecc …