Creare un 'RadioListBox' in WPF
Scritto da
Marco Amendola il
sabato 14 agosto 2010
•
Linguaggio:
• Livello: 200
A molti sarà capitato di aver bisogno di usare un insieme
coordinato di RadioButton all'interno di un'applicazione. Per
rendere un insieme di RadioButton mutuamente esclusivi, è
sufficiente impostare la proprietà "GroupName":
<RadioButton GroupName="myGroupName">a</RadioButton>
<RadioButton GroupName="myGroupName">c</RadioButton>
<RadioButton GroupName="myGroupName">b</RadioButton>
Questo consente di creare diversi gruppi coordinati in maniera
flessibile. Tuttavia, trattandosi di controlli isolati, è
complicato determinare l'elemento selezionato: occorre assegnare un
nome a ciascun RadioButton e testare la proprietà IsChecked per
ciascuno di essi.
Ancora più complicato utilizzare il valore selezionato oppure
caricare i valori disponibili attraverso il Data Binding, cosa
indispensabile in molte applicazioni "Line of Business".
Quello di cui abbiamo bisogno per questi scenari è qualcosa che
abbia un comportamento di questo tipo:
- consenta di rappresentare un insieme arbitrario di elementi
(magari fornito attraverso binding)
- consenta la selezione di un solo elemento alla volta
- permetta la "lettura" dell'elemento selezionato attraverso una
proprietà (che possa essere collegata con binding)
Ma un controllo del genere esiste già: è il ListBox. Si osservi
ad esempio la window seguente:
<Window x:Class="DomusDotNetPills.RadioListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pillola RadioListBox"
SizeToContent="Height"
Width="300">
<Window.Resources>
<!-- simula una fonte dati -->
<x:Array x:Key="myItems"
Type="sys:String"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>The</sys:String>
<sys:String>quick</sys:String>
<sys:String>brown</sys:String>
<sys:String>fox</sys:String>
</x:Array>
</Window.Resources>
<StackPanel>
<Label>Seleziona un elemento:</Label>
<ListBox Name="theListBox"
ItemsSource="{StaticResource myItems}">
</ListBox>
<Label>Elemento selezionato</Label>
<TextBox IsReadOnly="True"
Text="{Binding ElementName=theListBox, Path=SelectedItem}"></TextBox>
</StackPanel>
</Window>
La soluzione quindi è semplicemente dare al ListBox l'aspetto di
un "RadioListBox", ovvero disegnare ogni elemento come un
RadioButton e rappresentare la selezione di un elemento con
l'attivazione del corrispondente RadioButton, piuttosto che con
l'evidenziazione della riga. Definiamo perciò uno stile per il
ListBox, alterando il template degli elementi contenuti
(ListBoxItem):
<Window.Resources>
...
<Style x:Key="RadioListBoxItemStyle"
TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="Transparent">
<RadioButton IsChecked="{TemplateBinding IsSelected}"
Focusable="False"
IsHitTestVisible="False">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RadioListBoxStyle"
TargetType="{x:Type ListBox}">
<Setter Property="ItemContainerStyle"
Value="{StaticResource RadioListBoxItemStyle}" />
</Style>
...
</Window.Resources>
Alcune brevi precisazioni:
- RadioListBoxItemStyle
è lo stile che applicheremo agli elementi (ListBoxItem) contenuti
nel ListBox; questo stile imposta un'unica proprietà (Template):
stiamo infatti alterando il modo in cui gli elementi vengono
disegnati.
- <ControlTemplate TargetType="{x:Type
ListBoxItem}">
il ControlTemplate specifica quali controlli primitivi
saranno utilizzati nella visualizzazione degli elementi
ListBoxItem: in pratica stiamo fornendo il "calco" con il quale
saranno creati tutti i ListBoxItem.
Dentro al control template troviamo un RadioButton: lo useremo,
come anticipato, per rappresentare ciascun elemento; la proprietà
IsChecked del RadioButton è collegata (attraverso un
TemplateBinding) alla proprietà IsSelected del ListBoxItem: in
questo modo l'elemento selezionato sarà visualizzato con un radio
"pieno".
- Focusable="False"
Evita semplicemente che il RadioButton possa ricevere il focus: è
sufficiente che lo riceva il contenitore (ListBoxItem).
- IsHitTestVisible="False"
Evita che il RadioButton sia "sensibile" alle operazioni
del mouse; in pratica lo stiamo rendendo "passivo": ne capiremo
presto il motivo.
- <Border Background="Transparent">
L'impostazione di uno sfondo trasparente sotto al
RadioButton fa sì che le operazioni del mouse possano
"oltrepassarlo" e raggiungere il ListBoxItem che lo contiene;
questo (insieme all'aver reso il RadioButton "passivo" con
IsHitTestVisible="False") consente di "disattivare" il
comportamento predefinito del RadioButton stesso, cioè di
"accendersi" quando viene cliccato. E' importante notare che lo
stiamo utilizzando solo come rappresentazione della selezione:
inibendo i suoi comportamenti "propri" lasciamo che la gestione
della selezione al clic del mouse venga fatta dal ListBoxItem.
- <ContentPresenter />
In questo punto viene iniettato l'effettivo contenuto di
ciascun ListBoxItem
- RadioListBoxStyle
è lo stile che applicheremo al ListBox: stiamo stabilendo che agli
elementi contenuti nel ListBox (i ListBoxItem) venga applicato lo
stile RadioListBoxItemStyle da noi definito. Il ListBox, come tutti
gli ItemsControl, funziona con un corrispondente tipo di
"sottoelementi": quando elementi di altra natura (nel nostro
esempio delle stringhe) vengono aggiunti al ListBox, questi si
occupa di "wrapparli" in un corrispondente ListBoxItem in modo da
poterli gestire opportunamente; siamo quindi sicuri di poter
intercettare tutti i possibili contenuti del ListBox senza
Completiamo l'esempio applicando lo stile definito al
ListBox:
<ListBox Name="theListBox"
ItemsSource="{StaticResource myItems}"
Style="{StaticResource RadioListBoxStyle}">
ottenendo il risultato desiderato.
Questa tecnica è estremamente frequente in WPF: il codice dei
controlli, infatti, non definisce il loro aspetto visuale, ma solo
il loro comportamento; è possibile, quindi, dare un'aspetto grafico
differente ai controlli pur mantenendone inalterata la logica di
funzionamento. Si può affermare che WPF utilizza un pattern
Presentation Model (MVVM) per i suoi stessi controlli.
Tags: WPF,radiobutton,listbox,style