Passa ai contenuti principali

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.

Commenti

Post popolari in questo blog

VB.NET : Aggregare stringhe con LINQ

Tip facile facile, ma a qualcuno potrebbe servire. Supponiamo di avere una lista di stringhe (magari come risultato di una query LINQ) e di voler ottenere una stringa con la concatenazione delle stesse: Dim list = CreateList() Dim concatStr = (From s In list _ Select s).Aggregate( Function (currentString, nextString) currentString + nextString) MessageBox.Show(concatStr) Il metodo CreateList non ci interessa, in questo momento, ma crea una lista di oggetti String. Protected Function CreateList() As IEnumerable( Of String ) Dim list As String () = {" stringa1 ", " stringa2 ", " stringa3 ", " stringa4 ", " stringa5 "} Return list.AsEnumerable() End Function Questo metodo potrebbe restituire una qualsiasi lista di oggetti di cui, nella select successiva recuperiamo solo stringhe. La stessa tecnica è utilizzabile per concatenare stringhe inserendovi un carattere separatore Dim list = CreateList() Dim

VB.NET: SplashScreen con effetto fade-in

In questo post vorrei proporvi un modo per realizzare una splash screen per le nostre applicazioni Windows Form che appare progressivamente con un effetto fade. Supponiamo di avere il nostro progetto VB.NET in una soluzione Visual Studio 2008 in cui abbiamo il sorgente della nostra applicazione Windows Form. Inseriamo una splash screen utilizzando il menù Progetto->Aggiungi Nuovo Elemento e selezionando il tipo di elemento “Schermata Iniziale” A questo punto Visual Studio creerà, automaticamente, la schermata iniziale che possiamo personalizzare graficamente come vogliamo. Per poter fare in modo che questa finestra appaia nel momento in cui avviamo l’applicazione, è necessario aprire le proprietà del progetto e impostare la maschera di avvio: In questo modo, all’avvio dell’applicazione, la schermata appare immediatamente e scompare un attimo prima della visualizzazione della finestra dell’applicazione. Possiamo far apparire la schermata iniziale con un ef

VB.NET: Convertire un file DOC in RTF e PDF con office interop

In questo post vorrei proporvi del codice per poter convertire un file .doc in un file .rtf oppure .pdf utilizzando le API di interoperabilità di Office. Creeremo una classe, DocConverter, che esporrà le due funzionalità sopra citate. Cominciamo con il prevedere un attributo privato della classe che rappresenterà l’applicazione Word che utilizzeremo per la conversione. Creeremo l’istanza dell’attributo privato all’interno del costruttore della classe: Public Sub New () If Not CreateWordApp() Then Throw New ApplicationException(" Assembly di interoperabilità con Office non trovato! ") End If End Sub Private _wordApp As Word.ApplicationClass Protected Function CreateWordApp() As Boolean Dim retval = True Try _wordApp = New Word.ApplicationClass() _wordApp.Visible = False Catch ex As System.Exception _wordApp = Nothing retval = False End Try Return retval End Function La conve