DotDotNet: Evento Accesso ai dati

Ho partecipato al primo evento organizzato da DotDotNet in cui si parlava di accesso ai dati.

Com'è andata? Direi molto bene per essere il primo evento! Sono intervenuti nell'ordine Alberto Dallagiacoma a parlarci di NHibernate, Alessandro Scardova a parlarci del passaggio dai DataSet ad Entity Framework e Lorenzo Barbieri che ci ha illustrato i pregi e le funzionalità di Visual Studio DB Edition.

Gli speaker se la sono cavata egregiamente, tenendo conto che era il loro primo evento (beh Lorenzo a parte): possono sicuramente migliorare, ma hanno fatto molto meglio di quello che avrei potuto fare io :) :)

Il taglio delle sessioni a me è piaciuto: sessioni corte che introducono un argomento solo senza troppe pretese. Secondo me servono sessioni del genere per indirizzare le persone e renderle consapevoli di quello che offre la piazza.

La prossima volta cercherò di unirmi alla cena, così scambieremo due chiacchiere anche faccia a faccia.

 

In attesa del form on-line di adesione al gruppo, Compliementi ragazzi e Buon Lavoro.

 

P.S.

Fantastica l'idea di accogliere la gente coi ciccioli. Mi raccomando fatelo ancora perchè io stavolta non li ho mangiati :(

 

Matteo

 

Technorati Tag:

WPF e l'ereditarietà degli stili

Gli Style di WPF sono certamente uno strumento molto comodo. Una caratteristica interessante degli Style è che uno ne può estendere un altro.

Supponiamo di avere la seguente definizione:

<Style x:Key="MainControlStyle" TargetType="{x:Type Control}" > <Setter Property="FontSize" Value="10" /> <Setter Property="FontWeight" Value="Normal" /> <Setter Property="Padding" Value="2" /> </Style> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MainControlStyle}" > <Setter Property="FontWeight" Value="Bold" /> </Style>

Con la proprietà BasedOn del secondo Style indichiamo che il secondo stile "eredita" le proprietà impostate da MainControlStyle.

Cosa abbiamo prodotto? Abbiamo due stili: il primo applicabile ad ogni Control che imposta FontSize, FontWeight e Padding; il secondo, che verrà applicato ad ogni Label, che "sovrascrive" il FontWeight impostato dallo stile padre, forzandolo a Bold.

Per completezza faccio un esempio in cui il primo stile non abbia una chiave che lo identifichi:

<Style TargetType="{x:Type Label}" > <Setter Property="FontSize" Value="10" /> <Setter Property="FontWeight" Value="Normal" /> <Setter Property="Padding" Value="2" /> </Style> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource {x:Type Label}}" > <Setter Property="FontWeight" Value="Bold" /> </Style>

Una sola nota: il TargetType dello stile che eredita deve essere compatibile con il TargetType dello stile padre, in sostanza lo stesso tipo o una sottoclasse.

 

Matteo

 

Technorati Tag: ,,,

WPF - Usare più file di risorse

Può capitare di volere fare riferimento a più file di risorse all'interno di una Window o di uno UserControl e farli magari convivere con le risorse definite localmente. La classe ResourceDictionary ci viene incontro con la proprietà MergedDictionary.

<Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="../../MainResources.xaml" /> <ResourceDictionary Source="../../Styles.xaml" /> </ResourceDictionary.MergedDictionaries> <DataTemplate x:Key="MyTemplate"> .... </DataTemplate> </ResourceDictionary> </Window.Resources>

In questo modo possiamo gestire le nostre Resources come una ResourceDictionary che sarà il risultato dell'unione delle nostre risorse locali e le ResourceDictionary definite in file esterni.

 

Matteo

 

 

Caricare un Template in modo condizionale

Come avevo anticipato nel post precedente vorrei esporre un tip per poter selezionare un template per rappresentare un determinato dato in base ad una condizione.

Supponiamo di avere una situazione simile al post precedente, in cui ho un ViewModel che mi rappresenta un ordine: OrderViewModel. Voglio rappresentare OrderViewModel in tre modi diversi in base al fatto che esso rappresenti la scheda di un ordine, un ordine in modifica o un ordine nuovo.

Per fare ciò implemento tre View diverse con tre UserControl e le associo ad altrettanti ControlTemplate nel seguente modo:

<ControlTemplate x:Key="OrderDetailTemplate" TargetType="{x:Type Control}"> <View:OderDetailView /> </ControlTemplate> <ControlTemplate x:Key="OrderCreationTemplate" TargetType="{x:Type Control}"> <View:OrderCreationView /> </ControlTemplate> <ControlTemplate x:Key="OrderModifyTemplate" TargetType="{x:Type Control}"> <View:OderModifyView /> </ControlTemplate>

Perchè faccio tre ControlTemplate? Perchè in questo modo posso creare un DataTemplate associato ad OrderViewModel che carica il template corretto a seconda della condizione definita prima in questo modo:

<DataTemplate DataType="{x:Type ViewModel:OrderViewModel}"> <Control x:Name="control" Template="{StaticResource OrderDetailTemplate}" /> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=IsModifying}" Value="True"> <Setter TargetName="control" Property="Template" Value="{StaticResource OrderModifyTemplate}" /> </DataTrigger> <DataTrigger Binding="{Binding Path=IsNew}" Value="True"> <Setter TargetName="control" Property="Template" Value="{StaticResource OrderCreationTemplate}" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate>

Come si vede dal codice XAML per cambiare il Template associato al Control, usato dal DataTemplate per visualizzare il mio OrderViewModel, è sufficiente definire dei DataTrigger che verificano le proprietà di interesse (nel mio caso IsModifying e IsNew).

Un DataTrigger permette di definire delle condizioni sulle proprietà dell'oggetto rappresentato dal DataTemplate; quando la condizione è verificata vengono eseguiti i Setter contenuti al suo interno, che si occupano si settare il valore Value alla proprietà Property dell'elemento TargetName. Si deve tenere conto che i DataTrigger vengono eseguiti in cascata; vince quindi l'ultima condizione verificata. Nel mio caso è importante tenere conto dell'ordine perchè nel Model quando la proprietà IsNew è True, risulta True anche la proprietà IsModifying. Per ovviare all'inconveniente di avere più condizioni vere simultaneamente avrei potuto usare dei MultiDataTrigger per mettere più condizioni in And, in modo da renderle mutuamente esclusive.

 

Matteo

 

WPF in salsa MVVM e i template

Uno degli aspetti di WPF che più mi piace sono i template. Qui si trova una panoramica su ciò che si può fare.

I DataTemplate sono molto comodi quando usiamo MVVM; a parer mio, sono la risposta alla domanda: "Bene ora ho il ViewModel e la View. Come li relaziono tra loro?".

Come funzionano? Tramite i DataTemplate puoi creare una associazione tra uno specifico tipo di dato ed un template che lo rappresenta. Facciamo qualche esempio: supponiamo di avere un ViewModel di tipo CustomerViewModel e di volerlo associare alla sua View, uno UserControl chiamato CustomerDetailView; potremmo scrivere qualcosa del tipo:

<DataTemplate DataType="{x:Type ViewModel:CustomerViewModel}"> <View:CustomerView /> </DataTemplate>

Supponiamo invece di voler definire direttamente all'interno del DataTemplate la struttura del Template; possiamo farlo, per esempio, in questo modo:

<DataTemplate DataType="{x:Type ViewModel:CustomerViewModel}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> ... </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="Company Name" /> <TextBox Grid.Column="1" Text="{Binding Path=CompanyName}" /> ... </Grid> </DataTemplate>

In questo modo siamo andati a definire una regola di questo tipo: "quando ti do un CustomerViewModel tu rappresentalo come questo Template".

Questa regola vale all'interno dello scope di azione del template. Se, per esempio, volessimo che i nostri template siano condivisi da più parti della nostra applicazione, sarà sufficiente inserire la loro definizione all'interno di una ResourceDictionary, da includere nelle nostre Window o nei nostri UserControl. Se invece volessimo usare un determinato template solo per una Window o uno UserControl, basterà definirlo all'interno delle loro risorse locali.

Come facciamo quindi, in definitiva, ad usare questi template? Facciamo l'esempio più semplice ovvero visualizziamo dentro una Window il CustomerViewModel di cui sopra:

<Window x:Class="xxx.Presentation.WPF.UI.View.CustomerView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Window.Resources> <ResourceDictionary Source="../MainResources.xaml" /> </Window.Resources> <DockPanel> <HeaderedContentControl DockPanel.Dock="Top" Content="{Binding Path=Customer}" /> </DockPanel> </Window>

Impostando come proprietà Content dell'HeaderedContentControl il Binding corretto (in questo esempio supponiamo di avere come DataContext della Window una classe che contiene una proprietà Customer di tipo CustomerViewModel), visualizzeremo all'interno della Window il template definito sopra. Ovviamente non è obbligatorio usare un HeaderedContentControl.

L'esempio non è un caso eclatante; passiamo quindi ad un esempio un po' più articolato. Supponiamo di voler realizzare una OrderListView che contiene quindi una lista di ordini, con una sezione dedicata ai dettagli dell'ordine selezionato e che presenti un form di ricerca. Per realizzarla potremmo decidere di creare un ViewModel per ogni entità coinvolta: OrderViewModel, OrderSearchFormViewModel e un OrderListViewModel organizzato più o meno così:

public class OrderListViewModel { public OrderSearchFormViewModel SearchForm { get; set; } public List<OrderViewModel> Orders { get; set; } }

Il processo è poi analogo a quello precedente: ogni ViewModel ha una relativa View; questi sono collegati tra loro con dei DataTemplate. Possiamo quindi costruire uno UserControl in questo modo:

<UserControl x:Class="xxx.Presentation.WPF.UI.View.OrderListView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:View="clr-namespace:xxx.Presentation.WPF.UI.View"> <UserControl.Resources> <ResourceDictionary Source="../MainResources.xaml" /> </UserControl.Resources> <DockPanel> <Grid DockPanel.Dock="Top"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <ContentPresenter Grid.Row="0" Content="{Binding SearchForm}" /> <ListView Grid.Row="1" Name="OrderList" DataContext="{Binding Path=Orders}"> <ListView.View> <GridView> ... </GridView> </ListView.View> </ListView> <ContentPresenter Grid.Row="2" Content="{Binding Path=SelectedItem, ElementName=OrderList}" /> </Grid> </DockPanel> </UserControl>

In questo modo abbiamo creato un controllo che dispone i dati sulle tre righe della grid:

  • visualizzerà sulla prima riga il SearchForm e lo rappresenterà con il DataTemplate relativo
  • la stessa cosa farà per l'ultima riga che verrà visualizzata solo quando selezioniamo un ordine dalla lista
  • la ListView centrale invece provvederà a mostrare tutti gli ordini in una GridView: in questo caso il template viene definito in-line

Infine questo UserControl, che altro non è che la OrderListView, verrà associato a OrderListViewModel sempre tramite un DataTemplate e il giro ricomincia.

Quali sono, a mio parere, i vantaggi di questa soluzione? In sostanza tre:

  • Centralizzare l'associazione tra View e ViewModel, in modo da poterla modificare in un solo punto se necessario
  • La possibilità di legare strettamente il metodo di rappresentazione al tipo rappresentato: se nell'esempio di prima la lista di OrderViewModel contenesse istanze diverse, per esempio StockOrderViewModel e SalesOrderViewModel, rappresentate da View diverse, il ContentPresenter verrebbe rappresentanto sempre con la View corretta senza dover fare nessuna modifica al codice XAML
  • La possibilità di rendere condizionale la scelta del DataTemplate (questo punto lo discuterò in un prossimo post)

 

Matteo

 

Technorati Tag: ,,,

WPF DataGrid e aggiunta di righe ad ItemSource

Un piccolo tip per mostrare una riga di aggiunta elemento nella DataGrid del WPF Control Toolkit.

Ho una DataGrid il cui ItemSource è bindato ad una proprietà di tipo ObservableCollection<MioTipo>; inoltre la proprietà CanUserAddRows è impostata a True. Qualcosa di questo tipo insomma:

<ToolKit:DataGrid AutoGenerateColumns="False" CanUserAddRows="True" CanUserSortColumns="True" Margin="5,5,5,5" Name="Items" ItemsSource="{Binding Path=OrderItems}"> <ToolKit:DataGrid.Columns> ... </ToolKit:DataGrid.Columns> </ToolKit:DataGrid>

Ebbene la riga di aggiunta Item non veniva visualizzata sulla DataGrid. L'errore, come solito, era mio: in MioTipo avevo definito un costruttore con un parametro, mentre il motore di binding della DataGrid richiede che il tipo degli Item esponga anche il costruttore senza parametri, cosa abbastanza scontata. Ho aggiunto quindi il costruttore base e tutto ha cominciato a funzionare come previsto.

 

Matteo

 

XmlSerializer e serializzazione personalizzata

Personalizzare l'XML prodotto dalla serializzazione di una classe con XmlSerialization è motlo semplice; è sufficiente infatti far implementare alle classi coinvolte nella serializzazione l'interfaccia IXmlSerializer.

Voglio mostrarvi un esempio: ho avuto la necessità di serializzare una classe che conteneva alcune liste di altre classi; il documento prodotto non doveva raggruppare le varie liste in nodi che rappresentavano le proprietà, ma elencare tutte le istanze sotto il nodo radice.

La classi di esempio:

public class InterfaceIn : IXmlSerializable { public List<Field> Fields { get; set; } public List<Constant> Constants { get; set; } ... }

XML che dovrebbe essere prodotto:

<INTERFACEIN> <CONSTANT DESTINATION="COMMIT_FLAG" VALUE="X" /> <CONSTANT DESTINATION="SAVE_FLAG" VALUE="X" /> <FIELD NAME="DOC_NUMBER" DESTINATION="DOKNR" /> <FIELD NAME="DOC_PART" DESTINATION="DOKTL" /> </INTERFACEIN>

E uno stralcio di implementazione dell'interfaccia IXmlSerailizable delle classi coinvolte:

Esempio di deserializzazione di InterfaceIn

public void ReadXml(System.Xml.XmlReader reader) { reader.Read(); while (!reader.EOF) { if (reader.IsStartElement()) { switch (reader.Name) { case "FIELD": var f = new Field(); f.ReadXml(reader); this.Fields.Add(f); break; case "CONSTANT": var c = new Constant(); c.ReadXml(reader); this.Constants.Add(c); break; } } reader.Read(); } }

Esempio di serializzazione di InterfaceIn

public void WriteXml(System.Xml.XmlWriter writer) { foreach (var f in this.Fields) f.WriteXml(writer); foreach (var c in this.Costants) c.WriteXml(writer); }

Esempio di serializzazione di Field:

public void WriteXml(System.Xml.XmlWriter writer) { writer.WriteStartElement("FIELD"); writer.WriteAttributeString("NAME", this.Name); writer.WriteAttributeString("DESTINATION", this.Destination); writer.WriteEndElement(); }

In buona sostanza è sufficiente avere un po' di dimistischezza con l'uso degli Stream, per ottenere delle serializzazioni personalizzate.

 

Matteo

 

Technorati Tag: ,,

Aprire un file col programma predefinito

Ecco un piccolo tip per aprire un file in c#, in modo del tutto analogo alla cara vecchia ShellExecute

System.Diagnostics.Process proc = new System.Diagnostics.Process(); proc.StartInfo.FileName = path; proc.StartInfo.UseShellExecute = true; proc.Start();

Non so se sia l'unico modo; sicuramente è un modo semplice ed efficace

 

Matteo

 

Technorati Tag:

SqlServer Compact 32 bit in ambiente a 64 bit

Se vi capita di usare Visual Studio 2008 su una macchina con, ad esempio, un Windows Vista a 64 bit per creare una applicazione con SqlServer Compact a 32 bit, potreste imbattervi in un errore col seguente messaggio:

...Could not load file or assembly or one of its dependencies. An attempt was made to load a program with an incorrect format...

A me è successo e ci ho messo un po a capire che era colpa della mia disattenzione.

Le soluzioni sono due:

  • la prima è che se vi serve usare il SqlCompact a 32 bit si deve fare attenzione alla piattaforma di destinazione in fase di compilazione; non va più bene AnyCPU ma va cambiata ad x86.
  • la seconda è quella di usare SqlCompact a 64 bit

 

 

Matteo

 

Technorati Tag: ,

Routed Event e TreeView

I Routed Event sono stati introdotti con l'avvento di WPF; molto in sintesi, sono eventi che vengono propagati lungo tutto l'albero di controlli della nostra Window, ammettendo quindi gestori multipli: per esempio se abbiamo uno StackPanel con dentro un Button con dentro una Image, l'evento click generato dal click del mouse sulla Image verrà propagato anche al Button e in seguito allo StackPanel. Qui potrete trovare maggiori info.

Di seguito invece un piccolo tip per l'uso delle TreeView. Ho implementato un piccolo programmino che prevede la rappresentazione di dati in un albero e la gestione del Drag and Drop. A questo scopo ogni TreeViewItem ha associato all'evento Drop un delegato (lo stesso per ogni item). Il problema, se tale si può definire, è dato dal fatto che essendo Drop un Routed Event, ogni qualvolta l'utente rilascia (o meglio "Droppa" :) ), qualcosa sul TreeViewItem desiderato, la gestione viene propagata a di Parent in Parent sino alla radice, eseguendo quindi N volte lo stesso metodo. Evitare questo comportamento è semplice: RoutedEventArgs espone una proprietà booleana Handled, che permette di interrompere la propagazione dell'evento agli altri gestori. Essendo quindi il gestore designato nella mia soluzione il destinatario del Drop mi è bastato impostare Handled a true all'interno del delegato:

void node_Drop(object sender, System.Windows.DragEventArgs e) { ... e.Handled = true; }

 

Interessanti questi Routed Event, ma se non li segui... :)

 

Matteo

 

 

Technorati Tag: ,,