Silverlight, controllo DatePicker e formattazione customizzata (con problemi di focus)
Mi sono scontrato con questo problema qualche giorno fa, ma trovo il tempo e la voglia di parlarne solo questa sera. Dunque, il tutto gira attorno a Silverlight ed al controllo DatePicker, dichiarato nel namespace System.Windows.Controls. La cosa interessante di questo controllo è che in tempi rapidi possiamo ottenere una view Silverlight come questa:
che corrisponde più o meno alla seguente porzione di XAML:
<TextBox Grid.Row="0" Grid.Column="1" Height="26" /> <TextBox Grid.Row="1" Grid.Column="1" Height="26" /> <ctl:DatePicker Grid.Row="2" Grid.Column="1" Height="26" /> <TextBox Grid.Row="3" Grid.Column="1" Height="26" />
Tanto per chiarirci: la vista è una Grid, divisa in 2 colonne e 4 righe. Lo XAML qui sopra rappresenta il contenuto della colonna di destra, in pratica i 4 controlli di input: 3 TextBox per l’inserimento di cognome, nome e note, ed un DatePicker per l’inserimento della data di nascita. Nell’immagine qui sopra il DatePicker è evidenziato nel rettangolo verde.
Il controllo DatePicker è splendido e funziona bene, ma ha un piccolo difetto: non è possibile personalizzare il formato della data. Il controllo infatti dispone di una proprietà SelectedDateFormat, che però può valere solo Short o Long. Non potete specificare una string format customizzata, e quindi o vi vanno bene le due previste, oppure siete fregati. C’è una soluzione? Diciamo che googlando si trova un barbatrucco.
Questo barbatrucco consiste nell’usare due controlli separati invece di uno solo: più precisamente, una TextBox ed ancora il nostro DatePicker:
<TextBox Grid.Row="0" Grid.Column="1" Height="26" /> <TextBox Grid.Row="1" Grid.Column="1" Height="26" /> <toolkit:DockPanel Grid.Row="2" Grid.Column="1" LastChildFill="True"> <ctl:DatePicker toolkit:DockPanel.Dock="Right" Width="23" /> <TextBox toolkit:DockPanel.Dock="Left" Height="26" /> </toolkit:DockPanel> <TextBox Grid.Row="3" Grid.Column="1" Height="26" />
Notate come è cambiata la terza riga. Ho usato un DockPanel (panel che adoro, onestamente) e dentro ho inserito i due controlli. Giocando con la proprietà LastChildFill=”True” faccio in modo che la TextBox riempia tutto lo spazio disponibile, mentre del DatePicker faccio comparire solamente il minimo indispensabile, ovvero il calendarietto che l’utente deve cliccare per scegliere la data. Ovviamente in un ambiente reale, realizzato con MVVM, la TextBox ed il DatePicker sarebbero bindate alla stessa proprietà, con il vantaggio però che nel binding di una normale TextBox posso specificare la stringa di formattazione che voglio. La UI appare come segue:
Molto simile, praticamente nella UI non si vede alcuna differenza. Invece dal punto di vista della usability, una piccola differenza c’è. Se l’utente è abituato a navigare da un controllo all’altro premendo TAB, vedrà che c’è un piccolo problemuccio. La sequenza del TAB infatti è la seguente:
- TextBox del Cognome (come in figura)
- TextBox del Nome
- vuoto, boh, non si vede
- TextBox della Data di Nascita
- TextBox delle Note varie
Quel vuoto, boh, non si vede è ovviamente il DatePicker, che al momento opportuno si prende il focus. Piccola osservazione: graficamente questo focus non si vede, e soprattutto non c’è alcuna combinazione di tasti che fa apparire il calendario per scegliere la data, quindi sono obbligato ad usare il mouse. La conclusione di tutto ciò è la seguente: per passare dal Nome alla Data di Nascita l’utente deve premere due volte TAB, il che non va molto bene. Un utente evoluto magari capisce al volo, ma un altro magari no, e nonostante questo, diciamolo pure: è proprio brutto.
Come si risolve questa cosa? Beh, il DatePicker è un Control, quindi…direte voi…basta impostare IsTabStop=”False” sul controllo ed il gioco è fatto.
Falso.
Anche mettendo IsTabStop=”False” il DatePicker si prende il focus esattamente come prima. E quindi? Quindi vuol dire che qualcosa dentro il controllo ruba comunque il focus, anche se noi gli abbiamo detto di no. E quindi bisogna usare le armi pesanti, ovvero lavorare sul template interno del controllo.
Come fare? Seguite i semplici passi (cercherò di essere breve):
- Aprite Expression Blend 4.0
- Cominciate un nuovo progetto di tipo Silverlight 4.0
- Aggiungete alla MainPage un controllo DatePicker, avendo prima aggiunto nei riferimenti l’assembly
C:Program Files (x86)Microsoft SDKsSilverlightv4.0LibrariesClientSystem.Windows.Controls.dll - A questo punto cliccate con il destro sul controllo, andate su Edit Template e poi su Edit a Copy
- Assegnate pure un nome alla nuova risorsa Style che Blend sta per creare
- Passate alla vista XAML, copiate lo style DatePickerStyle1 ed incollate dentro Visual Studio 2010
Lo stile è come al solito piuttosto complesso, perchè contiene TUTTO ciò che serve al runtime di Silverlight per disegnare il controllo. Quindi avete colori, altri controlli, animazioni, effetti, etc. etc. Vi aiuterò e vi semplificherò la vista: con un pezzettino di altro codice (che vi risparmio) sono riuscito a capire qual’è il controllo che ruba il focus. Ed è esattemente il seguente:
<System_Windows_Controls_Primitives:DatePickerTextBox x:Name="TextBox" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" Padding="{TemplateBinding Padding}" SelectionBackground="{TemplateBinding SelectionBackground}"/>
Questo è il controllo maledetto che ruba il focus. E’ sufficiente mettere qui IsTabStop=”True” per risolvere tutti i vostri problemi. Fatto questo, il TAB funziona regolarmente, saltate da un controllo all’altro senza che nessuno vi rubi il focus, potete formattare le date come volete voi, etc. etc.
Alla prossima.