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

 
x