Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1909, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

WPF: il data-binding drag'n'drop in Visual Studio 2010 - prima parte (DataSet)

Una delle più importanti novità che avremo a disposizione in Microsoft Visual Studio 2010 è il data-binding di tipo drag’n’drop anche nelle applicazioni Windows Presentation Foundation, in maniera analoga a quanto già accadeva per le applicazioni Win Forms.

 

Di questa nuova caratteristica abbiamo avuto un assaggio nell’intervista che ho fatto a Milind Lele di Microsoft Corp. durante l’MVP Global Summit 2009, che desidero riprendere e approfondire verso altri contesti. Così com’è, questo tipo di data-binding ha dei limiti intrinseci, nel senso che va benissimo per applicazioni semplici, senza molte pretese, magari per la sola visualizzazione di dati tabulari. Tuttavia, oltre ad apprezzare lo sforzo che si è fatto in questo senso anche per WPF, la nuova caratteristica ha contestualmente, a mio avviso, molteplici vantaggi.

 

Innanzitutto, si può utilizzare con sorgenti dati differenti: ad esempio, posso utilizzare il drag’n’drop sia coi DataSet che con ADO.NET Entity Framework (sulle classi LINQ to SQL ci sono dei problemi nell’attuale Beta 1, presumo risolti in futuro). In secondo luogo, cosa fondamentale, permette a chi si avvicina all’accesso ai dati in WPF di capire come funzionano le cose e quali sono gli oggetti di “prima necessità”, anche se tutto è migliorabile e riscrivibile. Infine, altra cosa importante, nulla è rigido: possiamo infatti modificare quanto proposto dall’IDE a seconda delle nostre necessità, con qualche ritocco manuale (che poi è richiesto nella maggior parte dei casi).

 

Voglio quindi iniziare una mini-serie di post dedicati in modo specifico al data-binding di tipo drag’n’drop per WPF in Visual Studio 2010, suddividendo la trattazione in 4 parti:

 

1.    Nella prima, la presente, impareremo ad utilizzare il data-binding nei confronti di un DataSet generato a partire da un database di Access per generare una visualizzazione di dati in forma tabulare, sfruttando il nuovo controllo DataGrid. E’ questa una domanda che svariate volte ho sentito, quindi questo può essere un buon modo di parlare dell’una e dell’altra cosa;

 

2.    Nella seconda raggiungeremo l’identico obiettivo dei dati in forma tabulare, utilizzando però un modello a oggetti basato su Entity Framework per accedere a un db di SQL Server;

 

3.    Nella terza riprenderemo il discorso DataSet – Access per costruire una finestra un po’ diversa, sempre di tipo master-detail, senza rappresentazione tabulare ma coi singoli dettagli degli oggetti, implementando controlli per lo spostamento tra item;

 

4.    Nella quarta, faremo quest’ultimo discorso nei confronti di un modello basato su Entity Framework.

 

Per tutte le demo faremo utilizzo del database Northwind, sia nella sua versione per Access che in quella per SQL Server. Fatta questa premessa, iniziamo a mettere mano su Visual Studio 2010 Beta 1.

 

Creazione del progetto e connessione al database

 

Una volta aperto l’IDE, creiamo un nuovo progetto WPF in Visual Basic 2010, accertandoci che sia selezionato il .NET Framework 4.0 come target:

Fatto questo, aggiungiamo, come già sappiamo fare, una nuova origine dati utilizzando il comando Data|Add new data source. Nella prima schermata del wizard, selezioniamo Database:

Nella schermata successiva, dobbiamo per forza di cose selezionare il DataSet come oggetto da utilizzare:

Nella schermata successiva selezioniamo il database di nostro interesse, impostando le modalità di connessione:

Nell’ultima schermata selezioniamo le tabelle da rappresentare nel DataSet; utilizzeremo Customers (Clienti) e Orders (Ordini), questo per vedere in azione le novità della DataGrid:

A questo punto il DataSet viene generato con le consuete modalità, quindi non dobbiamo fare nulla. L’unica cosa importante da fare è compilare il progetto di modo che i riferimenti vengano aggiornati.

Apriamo, piuttosto, la finestra delle origini dati con il comando Data|Show Data Sources. Come possiamo notare, nella finestra delle origini dati compaiono gli oggetti corrispondenti alle nostre tabelle:

Immaginiamo ora di voler creare una finestra in cui da una ComboBox possiamo selezionare il cliente di nostro interesse da un elenco e, alla selezione, venga mostrato la lista degli ordini corrispondenti. Innanzitutto, nella finestra Data Sources espandiamo la voce dei Customers, quindi selezioniamo la voce CompanyName e modifichiamo la sua modalità di visualizzazione in ComboBox, come in figura:

 

Ora, col mouse facciamo clic su CompanyName e trasciniamolo sulla Window. Al rilascio, dovremmo ottenere un risultato simile al seguente (potete comunque spostare col mouse i vari oggetti):

Vi accorgerete subito che contestualmente Visual Studio ha generato del codice XAML che trasforma in markup il risultato della nostra azione:

 

<Window x:Class="Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="600" xmlns:my="clr-namespace:TabularDataDemoWithDataSet">

    <Window.Resources>

        <my:NorthwindDataSet x:Key="NorthwindDataSet" />

        <CollectionViewSource x:Key="CustomersViewSource" Source="{Binding Path=Customers, Source={StaticResource NorthwindDataSet}}" />

    </Window.Resources>

    <Grid>

        <Grid DataContext="{StaticResource CustomersViewSource}" Height="31.96" Margin="0,0,348,230" Name="Grid1"

              Width="230.136666666667">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="Auto" />

                <ColumnDefinition Width="Auto" />

            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>

                <RowDefinition Height="Auto" />

            </Grid.RowDefinitions>

            <Label Content="Company Name:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3"

                   VerticalAlignment="Center" />

            <ComboBox DisplayMemberPath="CompanyName" Grid.Column="1" Grid.Row="0" Height="23"

                      HorizontalAlignment="Left" IsSynchronizedWithCurrentItem="True"

                      ItemsSource="{Binding}" Margin="3" Name="CompanyNameComboBox" SelectedIndex="0"

                      VerticalAlignment="Center" Width="120">

                <ComboBox.ItemsPanel>

                    <ItemsPanelTemplate>

                        <VirtualizingStackPanel />

                    </ItemsPanelTemplate>

                </ComboBox.ItemsPanel>

            </ComboBox>

        </Grid>

    </Grid>

</Window>

 

E’ stato così generato un oggetto CollectionViewSource che, lato XAML, fa quello che fa in buona sostanza il BindingSource di Windows Forms, ossia fa da “ponte” tra i dati veri e propri e l’interfaccia. Viene definito nelle risorse e la sua sorgente è definita tramite binding sulla collezione Customers (Binding Path=Customers) esposta dal DataSet (Source={StaticResource NorthwindDataSet}).

 

Osserviamo poi come ci sia una Grid che contiene una Label e la ComboBox per la scelta. In tale Grid si assegna la proprietà DataContext, che alimenterà i controlli dipendenti e che punta proprio alla CollectionViewSource sopra definita. La ComboBox è popolata in binding ed è alimentata sempre dall’elenco dei Customers, dei quali, per ciascuno di essi, dovrà visualizzare il valore della proprietà CompanyName (DisplayMemberPath).

 

Torniamo alla finestra Data Sources e selezioniamo l’oggetto Ordes nidificato nel Customers, accertandoci che sia selezionata la visualizzazione come DataGrid:

Trasciniamo l’oggetto sulla Window, fino ad ottenere il seguente risultato:

Bello! Visual Studio ha generato una DataGrid ottenendo automaticamente i nomi delle colonne per noi. Ma non solo: ci ha risparmiato la fatica di generare le colonne a mano, escludendo dall’elenco anche quelle colonne che solitamente contengono collezioni di oggetti relazionati, quindi non visualizzabili.

 

Lato XAML, questo è il codice che l’IDE genera in questa fase:

 

        <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" Height="200"

                  HorizontalAlignment="Left" IsSynchronizedWithCurrentItem="True"

                  ItemsSource="{Binding}" Margin="0,34,0,0" Name="OrdersDataGrid"

                  RowDetailsVisibilityMode="VisibleWhenSelected" SelectedIndex="0" VerticalAlignment="Top" Width="400">

 

            <DataGrid.Columns>

                <DataGridTextColumn Binding="{Binding Path=OrderID}" Header="Order ID" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=CustomerID}" Header="Customer ID" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=EmployeeID}" Header="Employee ID" Width="SizeToHeader" />

                <DataGridTemplateColumn Header="Order Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=OrderDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="Required Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=RequiredDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="Shipped Date" Width="SizeToHeader">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <DatePicker SelectedDate="{Binding Path=ShippedDate}" />

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

                <DataGridTextColumn Binding="{Binding Path=ShipVia}" Header="Ship Via" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=Freight}" Header="Freight" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipName}" Header="Ship Name" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipAddress}" Header="Ship Address" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipCity}" Header="Ship City" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipRegion}" Header="Ship Region" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader" />

                <DataGridTextColumn Binding="{Binding Path=ShipCountry}" Header="Ship Country" Width="SizeToHeader" />

            </DataGrid.Columns>

        </DataGrid>

 

Al di là della lunghezza (quindi si apprezza ancora di più il lavoro dell’IDE), osserviamo che la generazione automatica delle colonne è impostata su False, quindi vengono creati appositi oggetti DataGridTextColumn. Notate come, nei campi riferibili ad oggetti DateTime, Visual Studio abbia automaticamente definito un template (DataGridTemplateColumn) per utilizzare un controllo DatePicker anzichè il solito TextBlock o TextBox. La DataGrid è in binding e le sue colonne sono popolate attraverso proprietà esposte dalla collezione associata.

 

Ma come viene effettivamente alimentata la griglia? Visual Studio ha generato un altro oggetto CollectionViewSource, che è definito nelle risorse:

 

        <CollectionViewSource x:Key="CustomersOrdersViewSource" Source="{Binding Path=CustomersOrders, Source={StaticResource

                              CustomersViewSource}}" />

 

E nella Grid principale ha poi assegnato la proprietà DataContext:

 

    <Grid DataContext="{StaticResource CustomersOrdersViewSource}">

 

Dobbiamo ora andare a vedere nel file di code-behind cos’è successo. Le operazioni di cui sopra hanno permesso all’IDE di generare il seguente codice Visual Basic:

 

    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

 

        Dim NorthwindDataSet As TabularDataDemoWithDataSet.NorthwindDataSet = CType(Me.FindResource("NorthwindDataSet"),

                                                                              TabularDataDemoWithDataSet.NorthwindDataSet)

        'Load data into the table Customers. You can modify this code as needed.

        Dim NorthwindDataSetCustomersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.CustomersTableAdapter = _

            New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.CustomersTableAdapter()

        NorthwindDataSetCustomersTableAdapter.Fill(NorthwindDataSet.Customers)

        Dim CustomersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersViewSource"),

                                                                              System.Windows.Data.CollectionViewSource)

        CustomersViewSource.View.MoveCurrentToFirst()

        'Load data into the table Orders. You can modify this code as needed.

        Dim NorthwindDataSetOrdersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter = _

            New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter()

        NorthwindDataSetOrdersTableAdapter.Fill(NorthwindDataSet.Orders)

        Dim CustomersOrdersViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CustomersOrdersViewSource"),

                                                                                          System.Windows.Data.CollectionViewSource)

        CustomersOrdersViewSource.View.MoveCurrentToFirst()

    End Sub

 

Come potete osservare, si tratta di codice che istanzia e popola il DataSet. Inoltre, viene ottenuta l’istanza delle CollectionViewSource in modo da poter ottenere un riferimento nel codice gestito ai dati. Tra l’altro, questo tipo di comportamento è in grado di porre in essere il data-binding two-ways, di conseguenza saremo in grado di persistere le modifiche fatte ai dati attraverso la DataGrid nei confronti della sorgente dati senza grosse difficoltà, come vedremo tra breve.

 

Questo è sostanzialmente tutto il codice generato per default. Va quindi, come potete intuire, modificato per esigenze proprie, prima fra tutte la eventuale necessità di filtrare i dati da visualizzare, attraverso una query, piuttosto che tutti i dati provenienti da una data tabella.

 

Torniamo ora allo XAML, poiché è nostra intenzione dare un aspetto più ordinato alla finestra aggiungendo, inoltre, un pulsante per salvare le modifiche ai dati. Innanzitutto, suddividiamo la Grid principale in tre righe:

 

    <Grid DataContext="{StaticResource CustomersOrdersViewSource}">

        <Grid.RowDefinitions>

            <RowDefinition Height="40" />

            <RowDefinition />

            <RowDefinition Height="40" />

        </Grid.RowDefinitions>

 

Quindi posizioniamo i controlli per la selezione del cliente all’interno della prima riga, rimuovendo proprietà per altezza, larghezza, margini:

 

        <Grid DataContext="{StaticResource CustomersViewSource}" Name="Grid1" Grid.Row="0">

 

Facciamo un lavoro analogo sulla DataGrid, che sarà posizionata nella riga centrale:

 

        <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" IsSynchronizedWithCurrentItem="True"

                  ItemsSource="{Binding}" Name="OrdersDataGrid"

                  RowDetailsVisibilityMode="VisibleWhenSelected" SelectedIndex="0" Grid.Row="1">

 

Da ultimo, aggiungiamo un pulsante nell’ultima riga, definito attraverso il seguente XAML:

 

        <Button Grid.Row="2" Width="100" Height="30" Content="Save changes" Name="SaveButton"/>

 

L’aspetto della nostra Window è ora il seguente:

 

Torniamo ora al codice Visual Basic, all’interno del quale dovremo eseguire alcune modifiche. Innanzitutto, spostiamo a livello di classe le dichiarazioni del DataSet e del TableAdapter per gli ordini, che attualmente sono contenute nel gestore Window1_Loaded:

 

    Private NorthwindDataSet As NorthwindDataSet

    Private NorthwindDataSetOrdersTableAdapter As TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter

 

modifichiamo le righe di codice che precedentemente istanziavano gli oggetti come nel modo seguente:

 

        Me.NorthwindDataSet = CType(Me.FindResource("NorthwindDataSet"), TabularDataDemoWithDataSet.NorthwindDataSet)

 

        Me.NorthwindDataSetOrdersTableAdapter = New TabularDataDemoWithDataSet.NorthwindDataSetTableAdapters.OrdersTableAdapter()

 

Scriviamo ora il gestore di evento clic per il Button:

 

    Private Sub SaveButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles SaveButton.Click

        Try

            Me.NorthwindDataSetOrdersTableAdapter.Update(Me.NorthwindDataSet.Orders)

            MessageBox.Show("Done!")

        Catch ex As Exception

            MessageBox.Show("An error occurred while attempting to save changes")

        End Try

    End Sub

 

Non è superfluo precisare che andrebbe fatto un po’ di refactoring sul codice per incapsulare le varie azioni all’esterno dei gestori di evento, ma ciò che dobbiamo capire bene ora è come funzionano i meccanismi di data-binding.

 

Ora proviamo ad avviare la nostra applicazione e.. magia!:

La nostra DataGrid offre la visualizzazione tabulare dei dati e ci consente di modificarli. Per quanto riguarda le date, è possibile utilizzare il DatePicker. Se proviamo a salvare le modifiche, queste verranno correttamente inviate al nostro database.

 

Concludiamo così questo primo, lungo post sul data-binding drag’n’drop in WPF 4.0. Come detto, il lavoro manuale susseguente a quello automatizzato può essere notevole, ma ritengo questo strumento davvero valido per capire alcuni meccanismi sul data-binding.

 

Nel prossimo post vedremo come cambiano le cose utilizzando ADO.NET Entity Framework in una tipologia di applicazione analoga. Il codice sorgente sarà reso disponibile per il download al termine della serie di post e verrà erogato tramite l’area Download di Visual Basic Tips & Tricks.

 

Alessandro

Print | posted on giovedì 4 giugno 2009 18:09 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET