Technology Experience

.NET World

Programmazione, libri, snippet di codice, articoli tecnici

.NET World

Domain Model, NHibernate (con lazy load = true) e WPF

Prevedo la stesura di un post piuttosto lungo. Cominciamo dagli ingredienti:

  1. Un’entità sul nostro domain model (Artista) definita in un assembly (DomainModel)
  2. Un assembly (DataAccessLayer) con un po’ di classi che, tramite NHibernate, si occupano di persistere le entità definite nell’assembly al punto (1) su database (SQL Server)
  3. Un altro assembly contenente un progetto WPF – dettagli più sotto

Tutto questo per garantire la separazione delle componenti software di un progetto: domain-model, dal e UI. Almeno queste. Non voglio fare il saccente, ma credo che la difficoltà sia Alta, ed i tempi di preparazione lunghi. E penso anche che darò un sacco di cose per scontate, e ciò non va bene. Pazienza. Mi piacerebbe estrapolare in un piccolo progetto la problematica che ho incontrato: e lo farò.

Introduzione
Supponiamo di avere una classe Artista che espone un certo numero di proprietà pubbliche. Tale classe fa parte del nostro domain-model. Tale classe è anche mappata, attraverso NHibernate, su un database SQL Server. Immaginatevi quindi proprietà come Cognome, Nome, DataNascita, Sesso, Disponibilita, tutte mappate su una tabella Anagrafica con i campi con lo stesso nome della proprietà, per semplicità.

Lo scopo è quello di far vivere un’istanza di Artista su una Window di WPF. Con “vivere” intendo l’intero ciclo di vita di un’istanza: creazione di un nuovo artista, inserimento valori, salvataggio. Oppure rilettura di una precedente istanza, modifica dei valori e salvataggio. Ignoriamo tutte le proprietà della classe Artista e teniamo solo Cognome.

Cominciamo.

La Window di WPF
Lo XAML è il seguente:

<Window x:Class="VivendoByte.CastingManager.TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Casting Manager" Height="300" Width="400" Background="LightCyan"> <TextBox x:Name="CognomeTextBox" Height="20" Margin="8" Text="{Binding Path=Cognome}" /> </Window>

L’unico elemento nella Window è una TextBox, che è bindata alla proprietà Cognome di un’ipotetica istanza di Artista. Diamo un’occhiata al code-behind di questa classe:

public partial class TestWindow : System.Windows.Window { Artista _artista; public TestWindow() { InitializeComponent(); _artista = new Artista("Liborio", "Damiani"); this.DataContext = _artista; } }

La Window ha un field privato _artista, che vive all’interno della Window stessa. Nel costruttore creo l’istanza, dando un nome ed un cognome fittizi. Poi dico che il DataContext della Window è l’istanza appena creata. Quando la Window appare su schermo, il risultato è il seguente:

La TextBox mostra il cognome. Adesso arriva il bello: passiamo ad usare NHibernate.

Utilizzare NHibernate con WPF
Senza scendere troppo nel dettaglio, usiamo NHibernate per caricare da db un’istanza precedentemente persistita. Nel costruttore, invece di fare new Artista(), diciamo ad un ipotetico data provider (ipotetico per voi, ma non per me) di caricare un’istanza: ad esempio, quella con ID = 11. Vediamo come modifichiamo il code-behind:

1 public partial class TestWindow : System.Windows.Window 2 { 3 Artista _artista; 4 DataProvider<Artista> _provider; 5 6 public TestWindow() 7 { 8 InitializeComponent(); 9 _provider = new DataProvider<Artista>(); 10 _artista = _provider.LoadObject(11); 11 this.DataContext = _artista; 12 } 13 }

Abbiamo un data provider generico, che viene istanziato nel costruttore. L’istanza di Artista viene caricata da database, prelevando quella con ID = 11. Infine viene impostato il DataContext della Window.

Qui abbiamo un grave problema. Se NHibernate è impostato per caricare le istanze con lazy=true, otteniamo un’exception, mentre se decidiamo di non utilizzare il lazy load tutto andrà a buon fine. Il tipo di oggetto ritornato dalla LoadObject(11) qui sopra dipende a seconda di come impostiamo il lazy:

Se Lazy Load = true, _artista.GetType() è CProxyTypeVivendoByte_CastingManager_DomainModelArtistaDomainModel_NHibernate_ProxyINHibernateProxy1

Se Lazy Load = false, _artista.GetType() è VivendoByte.CastingManager.DomainModel.Artista

Se utilizziamo il lazy load, quindi, NHibernate ci ritorna un tipo di oggetto che non è quello che ci aspettiamo per il data-binding. Ricordo in breve che il lazy load di NHibernate permette il caricamento ritardato di un’istanza di un oggetto – caricamento che avviene realmente solo nel momento in cui abbiamo effettivamente bisogno di utilizzarlo. Per esempio, se facciamo eseguire dal debugger la linea (10), vedremo come le proprietà non siano valorizzate come ci si aspetta, ma esse continuano a valore null; stessa cosa per i membri privati. In sostanza, il tipo ritornato con lazy=true non è nient’altro che una sorta di wrapper attorno all’istanza di Artista, un wrapper che si occupa di andare a leggere l’istanza tramite la session di NHibernate che deve rimanere aperta.
Il tipo di caricamento va impostato nel file di mapping di NHibernate, per ciascuna classe mappata:

<class name="Artista" table="Anagrafica" lazy="false"> ... ... </class>

Comunque, nel momento in cui tentiamo di impostare il DataContext della Window con un tipo non corretto, otteniamo una System.Reflection.AmbiguousMatchException. Un errore che sinceramente non sono riuscito a decifrare davvero fino in fondo. Ho litigato anche qui per un sacco di tempo, convintissimo che il problema fosse un mio non corretto utilizzo del DataContext di WPF, ma poi alla fin fine ho dovuto concentrarmi sul comportamento di NHibernate. E l’inghippo è saltato fuori, anche grazie all’aiuto di un amico.

Conclusione
Sono arrivato fin troppo velocemente alla fine. Credo ci siano ancora un milione di cose da dire. Note varie: la classe Artista di questo esempio implementa l’interfaccia INotifyPropertyChanged, per poter gestire correttamente il meccanismo di data-binding. Non appena reimposto il DataContext della Window, quindi, tutti i controlli riflettono il cambiamento, sia che l’istanza di Artista venga caricata da database oppure che venga creata direttamente una nuova istanza.

Technorati Tags:      

Send to Kindle
.NET World

Usare un DateTimeConverter nei progetti WPF

Eventuali imprecisioni sono dovute all’ascolto, durante la stesura del post, di un concerto del Liga.
Chiedo scusa: aiutatemi con i vostri commenti. 🙂

La classe DateTimeConverter è definita nel namespace System.ComponentModel nell’assembly System.dll. Il primo pensiero che ho avuto quando ho scoperto la presenza di questa classe – con questo nome – è stato: cavolo, posso metterla nelle risorse di una Window e sfruttarla nel meccanismo di data-binding tra un tipo DateTime ed un semplice controllo come la TextBox.

Supponiamo di avere una Window semplice definita così:

1 <Window x:Class="StudyWPF.DataBindingWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:cnv="clr-namespace:System.ComponentModel;assembly=System" 5 Title="StudyWPF" Height="300" Width="300"> 6 <Window.Resources> 7 <cnv:DateTimeConverter x:Key="cnvDateTime" /> 8 </Window.Resources> 9 </Window>

Ho aggiunto un riferimento al namespace System.ComponentModel dentro l’assembly System.dll con i metodi di cui abbiamo parlato più o meno tutti in passato. Due note. Primo: occhio, la “a” di “assembly” alla riga (4) nel mapping del namespace deve essere obbligatoriamente minuscola. Secondo: il riferimento all’assembly, sempre alla riga (4) non deve avere estensione. Ogni volta ci perso mezz’ora, spero che scrivendolo questo tempo diminuisca. 🙂

Fatto questo, ho definito fra le risorse un’istanza di DateTimeConverter, usando come key la stringa “cnvDateTime”. Se provate a compilare, non avrete alcun errore. L’istanza del converter viene creata. Ma se provate ad usarla in un qualche data-binding, fallirà. Ve lo dimostro, sperando di non dire qualche stupidaggine (in tal caso, leggere il prologo all’inizio). Guardate il seguente XAML:

<Window x:Class="StudyWPF.DataBindingWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:StudyWPF" xmlns:cnv="clr-namespace:System.ComponentModel;assembly=System" Title="StudyWPF" Height="300" Width="300" DataContext="{DynamicResource player}"> <Window.Resources> <s:HockeyPlayer x:Key="player" /> <cnv:DateTimeConverter x:Key="cnvDateTime" /> </Window.Resources> <TextBox Text="{Binding Path=Data}" /> </Window>

E’ una sorta di evoluzione di quello di prima. Fra le risorse c’è ancora il DateTimeConverter di prima. Ho aggiunto un nuovo namespace ed una nuova risorsa, per avere a disposizione un oggetto di business che mi vada ad esporre una proprietà pubblica di tipo DateTime. Tale oggetto è uno HockeyPlayer (ve lo ricordate?) e mi espone una semplice proprietà Data. Ho impostato il DataContext della Window sulla risorsa player. In questo modo alla Window posso aggiungere una TextBox bindata all’oggetto secondo il Path=Data. Cosa mostra la TextBox? E’ presto detto…

Ovvero…una semplice ToString() sull’oggetto definita nella risorsa, che giustamente ha i valori di default per un’istanza di DateTime. Supponiamo adesso di voler sfruttare il converter: modifichiamo la definizione del data-binding sulla TextBox:

<TextBox Margin="4" Text="{Binding Path=Data, Converter={StaticResource cnvDateTime}}" />

Al data-binding ho detto di usare un converter, e nello specifico quello definito dalla risorsa. Il codice non compila: il messaggio di errore è:

Cannot convert the value in attribute ‘Converter’ to object of type ‘System.Windows.Data.IValueConverter’. Object of type ‘System.ComponentModel.DateTimeConverter’ cannot be converted to type ‘System.Windows.Data.IValueConverter’.  Error at object ‘System.Windows.Data.Binding’ in markup file ‘StudyWPF;component/databindingwindow.xaml’ Line 15 Position 23.

Il messaggio è chiaro. WPF si aspetta converter che implementino l’interfaccia IValueConverter del namespace System.Windows.Data. La classe DateTimeConverter che abbiamo usato noi non fa nulla di tutto questo: grazie ad un giro con Reflector, vedo che deriva da TypeConverter, e non c’entra nulla.

Le soluzioni sono almeno due: o vi scrivete voi un DateTimeConverter adatto a funzionare con WPF (operazione semplicissima, al punto che è la strada che ho seguito), oppure date un’occhiata a questo progetto su CodeProject.com, che oltre al DateTimeConverter vi dà altri converter che potrebbero esservi utili in altri contesti.

Technorati Tags:   

Send to Kindle
.NET World

StatusBar gestita con la proprietà .Tag dei controlli sulla Window

Questa volta vi rimando direttamente al post del mio (nostro) amico Corrado, che è stato così gentile e paziente da sopportarmi sul Messenger per aiutarmi a venire a capo del (piccolo) problema che avevo. 🙂

Supponiamo di avere una Window con un certo numero di controlli (TextBox in primo luogo, ma non solo). Supponiamo di avere anche una StatusBar, con cui vogliamo dare alcuni tip all’utente che sta usando l’applicazione stessa. L’obiettivo era quello di mostrare sulla StatusBar un tip diverso a seconda della TextBox che ha il focus in quel momento. Dopo aver litigato con XAML per mezza giornata e ieri sera, il buon Corrado ha regalato la soluzione che ho linkato prima.

Solo alcune precisazioni. Riporto il codice dell’evento OnGotFocus:

private void OnGotFocus (object sender, RoutedEventArgs e) { sbi.Content = (sender as TextBox).Tag.ToString(); }

Questo event-handler è agganciato tramite stile al RoutedEvent GotFocus di tutte le TextBox sulla Window. Come dice giustamente Corrado, il testo da visualizzare nella StatusBar viene prelevato direttamente dalla proprietà Tag della TextBox, che nella fattispecie è il sender dell’evento. C’è un primo minuscolo problema: se nello XAML la proprietà Tag non viene impostata, essa vale null, per cui il codice qui sopra bomberebbe con una bella Exception! Nulla di che: lo si sistema con una if veloce. C’è anche un secondo problema: vorrei poter impostare la proprietà Tag anche su controlli diversi dalla TextBox ed ottenere lo stesso effetto sulla StatusBar. Nel codice qui sopra il cast bomberebbe: l’ideale è fare il cast verso Control, che espone Tag ed è “polimorfica” rispetto a tutti (?) i controlli su una Window.
Il codice riportato qui sopra è stato corretto come segue:

private void OnGotFocus(object sender, RoutedEventArgs e) { object tip = (sender as Control).Tag; if (tip != null) barra.Content = tip.ToString(); else barra.Content = string.Empty; }

Questo funziona a meraviglia. Ovviamente è necessario associare l’EventSetter anche sullo stile dedicato, per esempio, alle ComboBox:

<Style TargetType="{x:Type ComboBox}"> <Setter Property="Margin" Value="4" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="FontSize" Value="12" /> <EventSetter Event="GotFocus" Handler="OnGotFocus" /> </Style>

Finito! Adesso posso impostare il Tag su TextBox e ComboBox e la StatusBar mostra un piccolo tip sotto forma di stringa, che aiuta l’utente e gli dice cosa deve inserire in ogni campo.

Technorati Tags:   

Send to Kindle
.NET World

Progetto Family.Show

Da un post sono venuto a conoscenza del progetto Family.Show, un software WPF sviluppato dalla Vertigo, hostato su CodePlex. Family.Show è un software per gestire l’albero genealogico della propria famiglia ed è completamente open-source. Questo screenshot è meraviglioso ed almeno per me la dice lunga…

E il bello è che è installabile attraverso ClickOnce. L’ultima versione è la 2.0 del 17 Luglio 2007, quindi piuttosto recente. Non so voi, ma a me ha sempre appassionato la possibilità di poter disegnare ed elencare tutti i propri parenti, non fosse altro che mio padre ha una decina di sorelle ed un fratello, e quindi il numero di zii e cuginetti sale a dismisura, e ricordarsi dei compleanni è ancora peggio.

Technorati Tags:   

Send to Kindle
.NET World

Siccome non lo so fare, lo faccio con XAML

Con WPF possiamo lavorare con molti strumenti diversi: Visual Studio 2005 (con l’opportuno supporto per il .NET Framework 3.0), Visual Studio 2008 (ancora in beta 2), la suite Expression, etc. etc. WPF è una tecnologia nuova, quindi è probabile che in futuro usciranno altri strumenti dedicati a sviluppatori e a grafici. Come ho letto da qualche parte, usciranno anche strumenti per grafici, e sarà la prima volta che il lavoro di un grafico sarà codice sorgente vero e proprio, e non un mero insieme di bytes che formano un’interfaccia grafica.

Dal canto mio, sto rimbalzando usando un po’ VS2005 ed un po’ Expression. Petzold credo mi abbia lasciato un’eredità dal suo ultimo libro pesante: conosco troppo bene XAML. Al punto che quando sono dentro Expression e devo raggiungere un determinato risultato “visivo”, so chiaramente come farlo con XAML, ma non so come ottenerlo usando l’IDE di Expression. Il risultato di tutto questo è che premo F11, passo alla vista XAML, edito, premo F12 e controllo se quello che vedo a video è quello che volevo ottenere. Al punto che in molti casi perdo il vantaggio di star lavorando con un IDE avanzato, dal momento che edito il codice XAML direttamente. Tra l’altro, la vista XAML di Expression è spartana (come è giusto che sia, secondo me) – niente a che vedere con quella di Visual Studio che, tra le altre cose, ci dà un bel Intellisense che aiuta non poco. Ricordo Corrado agli ultimi Community Days, che faceva vedere come passare dall’uno all’altro con un ALT+TAB, perchè VS fa delle cose, mentre Expression delle altre. Mi riconosco perfettamente in questo scenario.

La morale di tutto questo è lavorare con XAML. Dopo un po’ ci si fa l’abitudine, sul serio, e se si ha un po’ di immaginazione, si riesce a capire davvero quello che si sta facendo. Lavorare direttamente con XAML dà inoltre quella strana sensazione di avere tutto sotto controllo, si è certi al 100% che il codice è pulito e non è inutilmente prolisso. Mi sembra di tornare indietro nel tempo, al millennio scorso, quando lavoravo con ASP: adesso che ci penso, adottavo lo stesso metodo, cioè quasi aprire i files ASP con il blocco note. Potevo usare anche all’ora Visual Studio, potevo usare InterDev (e solo Dio sa quante volte l’ho fatto!), potevo usare Dreamweaver e molti altri IDE…ma finchè potevo volevo sentire i bytes sotto le mie dita. Blocco note allora, il “semplice” editor di VS per creare gradienti e triggers oggi con WPF.

Technorati Tags:     

Send to Kindle
.NET World

Petzold, allora lo regali davvero!

Non so se il nostro amico Charles Petzold (CP) regalerà davvero il suo ultimo libro 3D Programming for Windows: Three-Dimensional Graphics Programming for the Windows Presentation Foundation a tutti, ma a qualcuno – nella fattispecie un certo Jeff –  sicuramente sì. E come altre persone, anche Jeff ha criticato abbastanza il precedente libro WPF. Ed oltretutto, il libro è anche autografato.

Insomma, Charles, aspetto il mio.

Technorati Tags:   

Send to Kindle
.NET World

Come gestire gli access key con WPF (usability, Label, TextBox e dintorni)

Chi di voi ha lavorato con Visual Basic 6.0, o comunque con altri linguaggi di programmazione, sa che è possibile impostare – su una Label per esempio – un access key, ovvero impostare una lettera che premuta insieme al tasto ALT della tastiera dia il focus ad un controllo sulla stessa Windows Forms. Supponiamo di avere una Label la cui caption sia “Nome : ” e supponiamo di voler fare in modo che, premendo ALT+N sulla tastiera, il focus finisca su una TextBox lì a fianco, cosicchè l’utente possa inputare il valore. In Visual Basic 6.0, questo si otteneva facendo precedere alla lettera interessata il carattere di ampersand (&) e facendo in modo che la TextBox fosse il controllo successivo all’ordine determinato dalla proprietà TabIndex dei controlli stessi.

In Windows Presentation Foundation, questa cosa la si risolve ancora una volta grazie all’utilizzo del data-binding. Vediamo come.

Innanzitutto, prendiamo una normalissima Windows di WPF, aggiungiamo una Label ed una TextBox, nel modo seguente:

<Window x:Class="VivendoByte.CastingManager.TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Casting Manager, ricerca avanzata" Height="300" Width="400"> <Window.Resources> <Style TargetType="{x:Type Label}"> <Setter Property="Height" Value="20" /> <Setter Property="Margin" Value="4" /> <Setter Property="Width" Value="80" /> <Setter Property="Padding" Value="2" /> </Style> <Style TargetType="{x:Type TextBox}"> <Setter Property="Height" Value="20" /> <Setter Property="Margin" Value="4" /> <Setter Property="Width" Value="200" /> </Style> </Window.Resources> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <Label Content="Nome : " /> <TextBox Name="txtRicercaNome" /> </StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="Cognome : " /> <TextBox Name="txtRicercaCognome" /> </StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="Data Nascita : " /> <TextBox Name="txtRicercaDataNascita" /> </StackPanel> </StackPanel> </Window>

Ho usato gli stili per uniformare l’aspetto delle Label e delle TextBox, settando margini, larghezze, altezze e così via. L’aspetto più importante è l’elenco dei controlli, che è una sorta di accoppiata di Label + TextBox affiancate le une con le altre.
Ecco uno screenshot:

Noi vogliamo che:

  1. Premendo ALT + N il focus finisca sulla prima TextBox dall’alto
  2. Premendo ALT + C il focus finisca sulla seconda TextBox dall’alto
  3. Premendo ALT + D il focus finisca sulla terza TextBox dall’alto

La risoluzione è semplice, rapida ed indolore. Occorre usare il carattere underscore (_) prima del carattere che vogliamo rendere sottolineato e che agisca con il tasto ALT per formare l’access key. Occorre poi impostare la proprietà Target della Label, facendola bindare al controllo su cui vogliamo trasferire il focus. Per brevità, riporto solo lo XAML relativo alle Label:

<Label Content="_Nome : " Target="{Binding ElementName=txtRicercaNome}" /> <Label Content="_Cognome : " Target="{Binding ElementName=txtRicercaCognome}" /> <Label Content="_Data Nascita : " Target="{Binding ElementName=txtRicercaDataNascita}" />

Quando lanciamo l’applicazione, le Label appaiono esattamente come prima, ovvero senza alcun carattere sottolineato. Se premiamo il tasto ALT, l’engine di WPF evidenzia il carattere sottolineandolo. Se insieme ad ALT premiamo anche la lettera (N, C o D), il focus viene trasferito sul controllo specificato tramite il binding stabilito sulla proprietà Target. Et voilà!

Maggiori informazioni a partire da http://msdn2.microsoft.com/en-us/library/ms752101.aspx

Technorati Tags:    

Send to Kindle
.NET World

Come specificare Padding e Margin

Come specificare Padding e Margin nello XAML di WPF
Ci sono molte cose che non mi entrano in testa. Per quante volte mi vengano spiegate, o per quante volte possa leggerle, non mi entrano in testa. Una di queste è il modo con cui specificare il Padding ed il Margin dei controlli WPF.

Il Padding rappresenta lo spazio interno tra il contenuto di un controllo ed i suoi bordi. Il Margin rappresenta lo spazio tra il bordo del controllo e quello che gli sta accanto. Ci sono diversi modi per specificare questo valore. Il modo più comune è quello di indicare un solo valore alla proprietà Margin o Padding, come nell’esempio qui sotto:

<Label Padding="0" Background="Orange">Cognome :</Label>

Impostare Padding = “0” significa che la distanza tra il testo “Cognome :” ed il bordo della Label stessa è pari a zero. In pratica la scritta è attaccata al bordo su tutti e quattro i lati. Proviamo ad editare a mano lo XAML in questo modo:

<Label Padding="6" Background="Orange">Cognome :</Label>

Questa volta abbiamo impostato Padding=”6″: ciò significa che ci sono 6 device-independent pixels tra la scritta ed il bordo della Label. Tale distanza è applicata in ugual misura su tutti e quattro i lati.

Fin qua le cose sono semplici. Il fatto è che possiamo specificare Padding e Margin con più di un valore, per indicare distanze diverse per i quattro lati. Mi spiego meglio. Supponiamo di avere il seguente XAML:

<Label Padding="0,10,20,30" Background="Orange">Data di Nascita :</Label>

A questa Label è stato dato un Padding=”0,10,20,30″. I valori vanno letti nell’ordine Left, Top, Right e Bottom (ed è questo quello che non mi ricordo mai). Quindi, il testo sarà appiccicato al bordo sinistro, sarà a 10 pixel dal bordo superiore, sarà a 20 pixel dal bordo destro e a 30 pixel dal bordo inferiore. La stessa logica vale ovviamente per la proprietà Margin.

C’è anche la possibilità di specificare due valori soltanto. Per esempio:

<Label Padding="30, 6" Background="Orange">Data di Nascita :</Label>

In questo caso il primo valore (30) viene usato per impostare il Padding a sinistra e a destra, mentre il secondo valore (6) viene usato per impostare il Padding per l’alto ed il basso.

Chi ci sta dietro? ThicknessConverter!
Qualcuno si chiederà come è possibile che WPF riconosca quelle stringhe e come faccia a convertirle in valori double per continuare il processo di layout dei pannelli e dei controlli. Il segreto sta nella classe ThicknessConverter, che si preoccupa di convertire dal tipo string al tipo Thickness e viceversa. Tale classe è contenuta nell’assembly PresentationFramework.dll del Framework 3.0. Mi sono divertito ad esplorarla velocemente con Reflector, e anche dal codice si deduce come faccia uso di un metodo internal FromString che tra le altre cose è soggetto alla localizzazione della nostra applicazione, dal momento che uno dei parametri in ingresso è un CultureInfo. E si vede chiaramente che a seconda del numero di valori presenti nella nostra stringa, viene utilizzato un costruttore diverso della classe Thickness.

Riassumendo: l’ordine
Cioè, quello che mi dimentico più spesso.

X = stesso valore su tutti e quattro i lati
X, Y = X per il Left ed il Right; Y sul Top ed il Bottom
X, Y, Z, K = Left, Top, Right e Bottom

Technorati Tags:  

Send to Kindle
.NET World

Un equilibro tra risorse locali e risorse globali

La maggior parte delle volte ho visto gli stili dei controlli WPF definiti all’interno della Window nella quale poi lo stile stesso viene utilizzato. Lo stile in questo caso può essere utilizzato solo in quella particolare Window nel quale abbiamo creato lo stile. Se lo stile ci serve in altre Window, siamo obbligati a fare un copia & incolla dello XAML dove ci serve, il che non è proprio il massimo.

In WPF possiamo inserire una risorsa all’interno del file App.xaml, all’interno del tag <Application.Resources></Application.Resources>. In questo modo lo stile è globale per tutta l’applicazione. Se inserissimo una risorsa con x:Key=”myTemplate”, potrei far uso di questa risorsa ovunque ce ne sia bisogno. Esiste una via di mezzo? Ovvero: posso definire una risorsa in modo tale che non sia globale, ma che allo stesso tempo non viva solo all’interno di una Window particolare? Posso essere più selettivo, posso dire in qualche modo…qui mi serve e qui no?

Ovvio che sì. Il trucco sta nell’aggiungere un file di risorse in formato XAML, che non fa altro che definire un ResourceDictionary all’interno del quale possiamo inserire tutte le risorse che vogliamo, stili compresi.

Lo XAML può essere editato a mano in tutti i modi che conosciamo:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ctl="clr-namespace:FlickrBrowser.Controls"> <Style TargetType="ListBoxItem"></Style> <Style TargetType="ListBox" x:Key="PhotosetListBoxTemplate"></Style> <Style TargetType="{x:Type ctl:FlickrMenuItem}"></Style> </ResourceDictionary>

In questo caso, il primo stile è associato al tipo ListBoxItem, il secondo è associato al tipo ListBox (ma ha anche una Key) e l’ultimo è associato alla classe FlickrMenuItem. Questa classe eredita da MenuItem: ho seguito i consigli e le correzioni del buon Corrado.

Detto questo, ho avuto un po’ di difficoltà nell’utilizzare questi stili nelle Window della mia applicazione. Innanzitutto, va detta una cosa: la Build Action del file XAML che contiene le risorse (supponiamo…SomeResources.xaml come nell’immagine sopra) va impostato su Resource, che è la cosa più comoda. Vi riporto un blocco tratto direttamente da MSDN che parla proprio di questo argomento:

For resources that are compiled as part of the project, you can use a relative path that refers to the resource location. The relative path is evaluated during compilation. Your resource must be defined as part of the project as a Resource build action. If you include a resource .xaml file in the project as Resource, you do not need to copy the resource file to the output directory, the resource is already included within the compiled application. You can also use Content build action, but you must then copy the files to the output directory and also deploy the resource files in the same path relationship to the executable.

I valori plausibili per la Build Action sono Resource e Content. E non bisogna usare Embedded Resource. Io ho utilizzato Resource.

Nelle Window dove ci interessa utilizzare le risorse occorre importare lo XAML che contiene le risorse che ci servono. Questo si traduce nel prendere una Window qualsiasi all’interno del progetto ed inserire fra le risorse:

<Window x:Class="FlickrBrowser.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Flickr Browser - Search Photoset" Height="600" Width="700" WindowStartupLocation="CenterScreen"> <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="SomeResources.xaml" /> </ResourceDictionary.MergedDictionaries> </Windows.Resources> </Window>

In pratica, si fa un merge tra il ResourceDictionary definito nella Window ed il ResourceDictionary definito nel file di risorse specificato nella proprietà Source. A questo punto tutto comincia a funzionare come al solito: se uno stile è associato ad un tipo, allora quello stile verrà utilizzato, come nel caso di ListBox. Se uno stile ha una Key, è sufficiente referenziarla con StaticResource, e così via.

E’ trovato molto utile tutto questo. Ci saranno n motivi per farlo: quello che mi piace di più è il poter effettivamente definire uno stile e il poterlo riutilizzare ovunque vogliamo, pur mantenendo una sorta di selezione accurata, perchè basta importare dentro ogni Window quella che effettivamente ci serve. Un giusto punto di equilibrio tra risorse locali e risorse globali.

Technorati Tags:   

Send to Kindle
.NET World

Dettagli tecnici su un ContextMenu personalizzato

 Da quando Roberto tartassò il mio ContextMenu alla cena degli ultimi Community Days, ho dedicato la mia vita a creare e a sviluppare un ContextMenu degno di tal nome. Non che ci voglia tanto con WPF, sono io che sono imbastito con gli strumenti grafici. E comunque Roberto un pochino aveva ragione: il ContextMenu al quale faceva riferimento era veramente orribile, a partire dall’impaginazione dei vari controlli. Dicevo: ho cercato di impegnarmi. Facciamo un passo alla volta.

Ho una normalissima Window che contiene una Grid con una normalissima TextBox.

<Grid HorizontalAlignment="Stretch" VerticalAlignment="Top"> <TextBox Margin="6" VerticalAlignment="Stretch" Height="22" Name="txtUsername" Text="SERVERUsername" Background="Salmon"> </TextBox> </Grid>

Non ho associato alcun ContextMenu alla TextBox: ciò significa che se lanciassi il progetto e cliccassi con il destro all’interno della TextBox mi apparirebbe il solito menù con le voci Taglia/Copia/Incolla. Adesso inseriamo nelle risorse della Window il ContextMenu con le voci che ci interessa creare.

<Window.Resources> <ContextMenu x:Key="menuDX"> <MenuItem Header="Apri Browser" /> <MenuItem Header="Apri Microsoft Word" /> <MenuItem Header="Apri Microsoft Excel" /> <Separator /> <MenuItem Header="Taglia" /> <MenuItem Header="Copia" /> <MenuItem Header="Incolla" /> </ContextMenu> </Window.Resources>

A questo possiamo impostare la proprietà ContextMenu della TextBox alla risorsa statica con Key = “menuDX“.

<Grid HorizontalAlignment="Stretch" VerticalAlignment="Top"> <TextBox ContextMenu="{StaticResource menuDX}" Margin="6" VerticalAlignment="Stretch" Height="22" Name="txtUsername" Text="MARTEIgor" Background="Salmon"> </TextBox> </Grid>

Fino a questo punto il ContextMenu non ha un effetto grafico particolare, perchè non abbiamo dato stili o template particolare e quindi WPF lo disegna nel modo che conosce. Per fare cose più succulente occorre settare la proprietà Header a qualcosa di diverso dal solito banale e triste testo. Andiamo con calma: potremmo scrivere il ContextMenu di prima in questo modo:

<Window.Resources> <ContextMenu x:Key="menuDX"> <MenuItem> <MenuItem.Header> <StackPanel Orientation="Horizontal"> <Ellipse Margin="4" Fill="Red" Width="10" Height="10" Name="Cerchio" /> <TextBlock Margin="4" Text="Apri Browser" Name="txtTextBlock" /> </StackPanel> </MenuItem.Header> </MenuItem> ... ... </Window.Resources>

Ho riportato solo il primo MenuItem per brevità. Ho settato Header ad uno StackPanel, che contiene un Ellipse ed una TextBlock. La stessa cosa andrebbe fatta anche per gli altri MenuItem, variando opportunamente il colore dell’ellisse (proprietà Fill) ed il testo della TextBlock (proprietà Text). Lungo ed un po’ prolisso. Ho migliorato le cose creando uno UserControl chiamato con molta fantasia CustomMenuItem il cui XAML è:

<StackPanel x:Class="WindowsApplication1.CustomMenuItem" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Orientation="Horizontal"> <Ellipse Margin="4" Fill="Red" Width="10" Height="10" Name="Cerchio" /> <TextBlock Margin="4" Text="Apri Browser" Name="txtTextBlock" /> </StackPanel>

Nel code-behind di questo UserControl ho fatto in modo di estrarre le dependency-property del colore dell’ellisse ed il testo, in modo tale che siano visibili anche all’esterno della classe ed impostabili dallo XAML di chi fa uso dello UserControl stesso.

public string Testo { get { return txtTextBlock.Text; } set { txtTextBlock.Text = value; } } public Brush ColoreCerchio { get { return Cerchio.Fill; } set { Cerchio.Fill = value; } }

Ho messo i nomi delle proprietà in italiano perchè stavo sviluppando e ragionavo in lingua madre. In pratica, ho estratto quello che prima avevo inserito nell’Header del menù per inserirlo in un controllo a sè stante. A questo punto la definizione del ContextMenu può far uso di questo UserControl in questo modo:

<Window.Resources> <ContextMenu x:Key="menuDX"> <MenuItem> <MenuItem.Header> <ctl:CustomMenuItem Testo="Apri blocco note" ColoreCerchio="Red" /> </MenuItem.Header> </MenuItem> <MenuItem> <MenuItem.Header> <ctl:CustomMenuItem Testo="Apri Microsoft Word" ColoreCerchio="Blue" /> </MenuItem.Header> </MenuItem> <MenuItem> <MenuItem.Header> <ctl:CustomMenuItem Testo="Apri Microsoft Excel" ColoreCerchio="Green" /> </MenuItem.Header> </MenuItem> </ContextMenu> </Window.Resources>

In questo modo l’aspetto di ogni MenuItem è inserito all’interno dello UserControl, per cui se volessimo cambiarlo non dovremmo toccare le risorse della Window, ma modificare lo UserControl. Non che fosse una novità, ma l’ho detto lo stesso. Ho provato qualche elaborazione diversa usando template e TemplateBinding ed ho raggiunto praticamente gli stessi risultati, ma con qualche spunto in più su cui vorrei ragionare. Se ce la faccio, con questo caldo…

Ne parlerò fra un po’!

Technorati Tags:  

Send to Kindle