mercoledì 3 marzo 2010

Appunti di WPF – Quinta Puntata – XAML, proprietà ed eventi

Prendiamo in esame come vengono impostate le proprietà a livello di XAML in una finestra WPF.

Prendiamo in esame la semplice finestra:

  1. <Window x:Class="SimpleWindow"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Simple Application" Height="350" Width="525">
  5.     <Grid Background="yellow">
  6.         <Grid.RowDefinitions>
  7.             <RowDefinition Height="*" />
  8.             <RowDefinition Height="*" />
  9.             <RowDefinition Height="*" />
  10.         </Grid.RowDefinitions>
  11.         <TextBox x:Name="txtNome" Grid.Row="0" Margin="10">asaasa</TextBox>
  12.         <Button x:Name="btnInvia" Grid.Row="1" Margin="10">Premi</Button>
  13.         <TextBlock x:Name="txtMessaggio" Grid.Row="2" Margin="10" Background="LightGray" ></TextBlock>
  14.     </Grid>
  15. </Window>

La finestra è composta da una griglia di tre righe al cui interno troviamo, rispettivamente un TextBox, un Button e un TextBlock.

Vedremo in dettaglio il funzionamento dei controlli in un tutorial successivo, per ora ci basta sapere che il TextBox e il Button hanno la funzione già nota agli sviluppatori Windows Forms, mentre il controllo TextBlock è una sorta di Label.

La seguente figura mostra come si presenta l’interfaccia:

WPF_05_XAML_ProprietaEventi_Fig1

Analizziamo, quindi, come vengono definite le proprietà dei vari controlli e della finestra all’interno del file XAML.

Possiamo suddividere le proprietà presenti nel codice in tre differenti tipologie che vedremo in dettaglio.

Alcune proprietà vengono impostate utilizzando degli attributi dei tag XML che rappresentano i controlli. Un esempio di questo tipo di proprietà è il background della griglia o del TextBlock:

  1.  
  2. <Grid Background="yellow">
  3. <TextBlock x:Name="txtMessaggio" Background="LightGray" ></TextBlock>
  4.     

Alle spalle di questo semplice modo di definire una proprietà di un controllo WPF c’è la necessità, da parte del framework, di convertire la stringa del valore della proprietà nell’effettivo oggetto della proprietà stessa. Ad esempio nell’impostare lo sfondo della griglia, diciamo che tale sfondo (di tipo Brush) è “Yellow” ma “Yellow” è una stringa e il framework deve sapere come “convertire” la stringa nell’oggetto Brush. Per risolvere questo problema WPF utilizza i Type Converters (le cui basi sono presenti nel framework .NET fin dalla versione 1.0).

Un Type Converter mette a disposizione dei metodi che consentono di convertire un oggetto in un altro e viceversa. In questo caso, essendo i valori delle proprietà indicati con delle stringhe, i metodi trasformano la stringa in un opportuni oggetti.

Il parser XAML di WPF, quando deve impostare una proprietà di un controllo, esegue i seguenti passi:

1. Esamina la dichiarazione della proprietà per verificare se è presente l’attributo TypeConverter che indica, se presente, quale classe si occupa della conversione. Se tale attributo è presente, il framework utilizza l’opportuno TypeConverter per effettuare la conversione ed assegnare la proprietà;

2. Se la proprietà non ha l’attributo TypeConverter, il framework verifica l’eventuale attributo TypeConverter presente nella definizione della classe tipo della proprietà. Se presente, il framework utilizza la classe definita in questo attributo per la conversione e la relativa assegnazione;

3. Se non sono verificati i primi due passi, si ottiene un errore.

Ad esempio, se utilizziamo Reflector per capire come è definita la proprietà Background dell’oggetto TextBlock otteniamo:

  1. Public Property Background As Brush
  2.     Get
  3.         Return DirectCast(MyBase.GetValue(TextBlock.BackgroundProperty), Brush)
  4.     End Get
  5.     Set(ByVal value As Brush)
  6.         MyBase.SetValue(TextBlock.BackgroundProperty, value)
  7.     End Set
  8. End Property

La proprietà non è decorata con il TypeConverterAttribute (passo 1) e si procede alla verifica della classe Brush, la cui definizione è la seguente:

  1. <Localizability(LocalizationCategory.None,
  2. Readability:=Readability.Unreadable),
  3. TypeConverter(GetType(BrushConverter)), ValueSerializer(GetType(BrushValueSerializer))> _
  4. Public MustInherit Class Brush
  5.     Inherits Animatable
  6.     Implements IFormattable, IResource
  7.  
  8. .
  9.  
  10. .
  11.  
  12. End Class

La classe ha l’attributo TypeConverter che indica quale classe utilizzare per convertire i valori delle proprietà (BrushConverter in questo caso).

Definire le proprietà utilizzando gli attributi dei tag è banale quando si tratta di valori semplici ma questo meccanismo non è più utilizzabile nel momento in cui siamo di fronte ad attributi complessi.

Osserviamo, ad esempio, la definizione delle righe della griglia contenitore:

  1. <Grid Background="yellow">
  2.     <Grid.RowDefinitions>
  3.         <RowDefinition Height="*" />
  4.         <RowDefinition Height="*" />
  5.         <RowDefinition Height="*" />
  6.     </Grid.RowDefinitions>
  7.     .
  8.     .
  9.     .
  10. </Grid>

In questo caso la collezione delle righe (RowDefinitions) è composta da tanti oggetti RowDefinition, si tratta in sostanza di un oggetto complesso non rappresentabile facilmente con una stringa.

Per l’assegnazione di proprietà complesse si sfrutta il fatto che, trattandosi XAML di un file XML, possiamo innestare tag XML all’interno di altri tag. La convenzione utilizzata per le proprietà complesse è quella di inserire un tag composto dal nome del tag di cui si sta impostando la proprietà seguito da un “.” E, infine, dal nome della proprietà. All’interno di questo tag definiamo l’oggetto o gli oggetti costituenti il valore della proprietà.

Tanto per chiarire ulteriormente la situazione, supponiamo di voler cambiare lo sfondo del TextBlock e farlo diventare un gradiente con i colori bianco e giallo. In questo caso scriveremo:

  1. <TextBlock x:Name="txtMessaggio" Grid.Row="2" Margin="10">
  2.     <TextBlock.Background>
  3.         <LinearGradientBrush>
  4.             <LinearGradientBrush.GradientStops>
  5.                 <GradientStop Color="white" Offset="0"></GradientStop>
  6.                 <GradientStop Color="Yellow" Offset="0.5"></GradientStop>
  7.                 <GradientStop Color="white" Offset="1"></GradientStop>
  8.             </LinearGradientBrush.GradientStops>
  9.         </LinearGradientBrush>
  10.     </TextBlock.Background>
  11. </TextBlock>

Questa volta la proprietà Background dell’oggetto TextBlock è un oggetto LinearGradientBrush la cui proprietà GradientStops è, a sua volta, composta da 3 oggetti di tipo GradientStop. Graficamente otteniamo:

WPF_05_XAML_ProprietaEventi_Fig2

Grazie a questo modo di impostare le proprietà, possiamo ottenere delle composizioni di oggetti anche complesse mantenendo il file XAML abbastanza comprensibile.

Ultima categoria di proprietà che possiamo trovare all’interno di un file XAML è quella definita come Attached Property. Per capire di cosa si tratta, osserviamo la definizione del bottone della precedente interfaccia grafica:

  1. <Button x:Name="btnInvia" Grid.Row="1"
  2.         Margin="10">Premi</Button>

Tra le proprietà di questo oggetto troviamo l’assegnazione Grid.Row=”1”. La proprietà Grid.Row non esiste nella classe Button, e, infati, la sintassi con il “.” sta ad indicare che tale proprietà non è dell’oggetto a cui la imposta ma è legata contenitore dello stesso gerarchicamente superiore.

In pratica, ogni controllo ha le sue proprietà e, quando viene posizionato all’interno di un contenitore acquisisce altre proprietàm del contenitore stesso.

Le attached properties non sono effettivamente delle proprietà come quelle usuali ma si traducono nella chiamata di opportuni metodi. Il parser dello XAML, quando incontra una attached property, richiama il metodo statico SetNomeProprietà() della classe contenitore. Nell’esempio precedente, quando il parser XAML incontra l’assegnazione Grid.Row=”1”, effettua la chiamata al metodo statico Grid.SetRow(). Se osserviamo (tramite Reflector) come è realizzato il metodo statico di cui sopra, otteniamo:

  1. Public Shared Sub SetRow(ByVal element As UIElement, ByVal value As Integer)
  2.     If (element Is Nothing) Then
  3.         Throw New ArgumentNullException("element")
  4.     End If
  5.     element.SetValue(Grid.RowProperty, value)
  6. End Sub

Quindi, effettivamente, il valore “1” non viene memorizzato all’interno della griglia ma del bottone (in questo caso il parametro Element è il nostro Button) tramite il metodo SetValue() (che, in realtà, è un metodo della classe DependencyObject da cui UIElement indirettamente deriva).

Utilizzando le attached properties ci garantiamo la possibilità di introdurre, in futuro, nuove proprietà in nuovi contenitori senza dover toccare i vecchi controlli.

Gli attributi dei tag XAML possono essere utilizzati per definire eventuali gestori di eventi dei controlli.

Supponiamo di voler definire il gestore di evento per il click del pulsante inserito nella finestra di prova vista in precedenza, scriveremo:

  1.  
  2. <Button x:Name="btnInvia" Grid.Row="1"
  3.         Margin="10" Click="btnInvia_Click">Premi</Button>

Per completare il processo dovremmo, ovviamente, definire il gestore di evento nel code-behind:

  1. Private Sub btnInvia_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  2.  
  3. End Sub

In maniera analoga a quanto avviene nelle Windows Form, quando l’utente effettuerà un click sul bottone, verrà invocato il metodo btnInvia_Click. Osserviamo che, a differenza delle Windows Form, il secondo argomento del gestore di evento è di tipo RoutedEventArgs. L’event routing è il nuovo modello di gestione degli eventi che vedremo in un successivo tutorial.

A differenza delle Windows Form, non è necessario assegnare un nome ad un controllo per definire il gestore di evento tramite l’attributo.

Possiamo definire il gestore di evento di un controllo anche nella maniera canonica con la clausola Handles (come nelle Windows Form). In questo caso il controllo deve necessariamente avere un nome e non serve definire l’attributo nel tag XAML:

  1. Private Sub btnInvia_Click(ByVal sender As System.Object,
  2.                            ByVal e As System.Windows.RoutedEventArgs) Handles btnInvia.Click
  3.  
  4. End Sub

Scarica la versione PDF dell'articolo. Scarica la versione Amazon Kindle dell'articolo.

Nessun commento: