Creare una griglia in WPF per visualizzare, modificare e
paginare dati ottenuti con LINQ-to-Entities in VB 2008

a cura di Alessandro Del Sole (requisiti: conoscenze di base su Windows Presentation Foundation e LINQ))

Introduzione
Come detto in molte occasioni, Windows Presentation Foundation non è solo una tecnologia in grado di offrire supporto per elevati contenuti multimediali e interfacce utente strabilianti. Infatti, WPF mette a disposizione tecniche molto sofisticate per la visualizzazione e la modifica dei dati sfruttando un motore di data-binding molto potente. Al tempo stesso, una volta entrati nell'ottica di come ciò si realizza, ci si rende conto di come queste operazioni siano sostanzialmente semplici.

Dopo aver visto in un precedente articolo come collegare una origine dati prodotta con LINQ-to-Objects a una ListView per la visualizzazione dei dati ottenuti, in questo articolo il discorso diventa decisamente più complesso e articolato, ma molto più interessante. Ci occuperemo, infatti, di creare un'applicazione WPF in grado di leggere il contenuto di una tabella esposta da un database di SQL Server, per la quale verrà generato un modello a oggetti basato su ADO.NET Entity Framework; la tabella verrà interrogata tramite LINQ-to-Entities (il paradigma di LINQ che permette di dialogare con l'Entity Framework) e i dati verranno mostrati in una ListView. La cosa più interessante, però, è che renderemo i campi della ListView modificabili, in modo da poter eseguire l'editing dei dati e il successivo reinvio al database, mettendo in atto il tipo di data-binding definito Two-way. Infine, giusto perché le cose troppo semplici non ci piacciono, sfrutteremo LINQ per eseguire la paginazione dei dati, al fine di mostrare solo un determinato numero di record, implementando controlli che ci permettano di andare avanti e indietro nella visualizzazione di tutti gli oggetti.

Detto così, sembra tutto molto difficile. In realtà, nel corso dell'articolo vedremo come questo sia ottenibile senza troppo sforzo, ammesso che abbiate delle conoscenze di base su WPF e LINQ.
Il codice sorgente a corredo dell'articolo è, come di consueto, disponibile nell'area download di Visual Basic Tips & Tricks, ma in questa occasione è davvero importante che seguiate i passaggi illustrati per capire i numerosi concetti introdotti.

Cosa occorre
Per realizzare l'obiettivo proposto è necessario aver installato Visual Studio 2008 Service Pack 1, che aggiunge il supporto per ADO.NET Entity Framework più alcune migliorie a WPF di cui faremo utilizzo. Va bene, ovviamente, anche Visual Basic 2008 Express Edition con Service Pack 1. Inoltre, per la dimostrazione si farà utilizzo del database dimostrativo Northwind, che potete trovare nella MSDN Code Gallery.

Creazione del progetto
La prima operazione da compiere è creare un nuovo progetto WPF in Visual Basic. Dopo aver aperto Visual Studio 2008, create un nuovo progetto mediante il comando File|New project. Quando compare la finestra New Project, selezionate il modello WPF Application e assegnate al progetto il nome WPFTabularEntries, come in figura:

Dopo alcuni secondi il nuovo progetto è disponibile nell'IDE. L'operazione successiva è quella di aggiungere l'Entity Data Model al nostro progetto.

Aggiunta dell'Entity Data Model
ADO.NET Entity Framework offre l'infrastruttura per lavorare sui dati in maniera orientata agli oggetti, attraverso un modello basato su entità. Se avete utilizzato LINQ-to-SQL, molti concetti vi saranno già familiari, sebbene le differenze architetturali siano molto profonde. Non è possibile in questa sede discutere in maniera approfondita le peculiarità dell'Entity Framework (lo faremo in altri articoli, anche se potete trovare opportuni approfondimenti nel mio libro su LINQ), pertanto ci limitiamo a descrivere le caratteristiche principali.
In breve, ADO.NET Entity Framework permette di eseguire una procedura di mapping grazie al quale si ottiene:

In questo modo non si lavora sul database ma sugli oggetti che lo rappresentano, sfruttando un modello di elevata astrazione. Al termine dell'articolo troverete una serie di link a risorse utili per approfondire il discorso sull'Entity Framework. Ciò premesso, attraverso la finestra Server Explorer (o Database Explorer in VB Express), stabilite una connessione al database Northwind (nel sorgente a corredo il database è stato incluso, per vostra comodità, in una cartella del progetto chiamata Data). Quando la connessione è stabilita, dal menu Project selezionate il comando Add new item. Quando compare l'omonima finestra, selezionate la scheda Data, quindi il modello ADO.NET Entity Data Model, al quale assegnerete il nome Northwind.edmx come in figura:

È importante ricordare che il nome dell'Entity Data Model deve essere uguale a quello del database. Fate clic su Add per avviare la procedura guidata per la selezione dei dati. La prima schermata vi chiederà se il modello a oggetti deve essere creato a partire da un database esistente o da zero. Lasciate invariata la selezione sul db esistente e fate clic su Next. La schermata successiva richiederà di verificare le proprietà della connessione e si presenta come in figura:

Verificate quindi che il nome e il percorso del database siano corretti. Notate come Visual Studio proponga un identificatore di default (in questo caso NORTHWNDEntities) per la memorizzazione delle impostazioni di connessione nel file di configurazione. Tale identificatore corrisponderà, inoltre, a quello dell'oggetto di tipo ObjectContext che ci permetterà di gestire le entità. Lasciatelo invariato e fate clic su Next. L'ultima schermata della procedura guidata ci richiederà di specificare le tabelle da includere nel modello a oggetti. Per semplicità, faremo sì che la nostra applicazione lavori con una sola entità; in questo modo capiremo i concetti fondamentali, per poi essere in grado, in futuro, di utilizzarli in relazioni di tipo master-detail. Ipotizzando di voler visualizzare l'elenco dei prodotti della nostra azienda di fantasia, espandete la cartella delle tabelle e selezionate la tabella Products:



L'identificatore per il Model Namespace va lasciato invariato. Il Model Namespace è, in sostanza, un namespace che espone sia la classe di tipo ObjectContext sia tutte le classi/entità che mappano le tabelle del database.

Fate clic su Finish e attendete alcuni secondi per vedere il risultato della procedura di mapping, come in figura.

 

Abbiamo così a disposizione un designer ORM (Object Relational Mapping) anche in ambito ADO.NET Entity Framework, grazie al quale possiamo gestire le varie entità, intervenendo sulla Finestra delle Proprietà piuttosto che sul codice Visual Basic che è stato generato dietro le scene per la rappresentazione in forma di oggetto di ciascuna tabella.

Come già detto, non è possibile in questa sede riassumere tutte le peculiarità dell'Entity Framework e degli strumenti offerti da Visual Studio 2008 allo scopo. Ci dobbiamo quindi limitare a descrivere quelli che utilizzeremo in questo articolo, ma prometto che in altre occasioni approfondiremo l'argomento in modo più dettagliato.

A questo punto abbiamo la sorgente dati su cui lavorare. Non ci resta che predisporre la parte WPF della nostra applicazione.

Creazione dell'interfaccia e data-binding dichiarativo
Fate ora doppio clic, in Solution Explorer, sul file Window1.xaml per attivare il designer WPF relativamente alla finestra principale della nostra applicazione. L'interfaccia sarà piuttosto semplice: avremo una ListView per la visualizzazione e la modifica dei dati più alcuni pulsanti per l'aggiunta, rimozione e salvataggio dei dati, nonchè due pulsanti per eseguire la paginazione. In primo luogo, ingrandiamo un pochino la finestra modificandone anche il titolo:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Editing Tabular Data in WPF" Height="400" Width="640">

Ora, suddividiamo il contenitore Grid proposto per default in due righe:

    <Grid>
        <!--Suddivido la Grid in due righe-->
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>

La prima riga della griglia conterrà la ListView. Ci sono alcuni commenti nel codice, che approfondiremo di seguito:

        <!--Nella prima riga metto una ListView-->
        <!--L'attributo Path per il Binding è vuoto perché lo assegno da codice
            ma intanto dico a WPF che l'origine dati è in binding-->
        <ListView Grid.Row="0" Name="ProductsListView"
                  BorderBrush="Black" BorderThickness="1" 
                  IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=''}">

Oltre all'assegnazione di un nome, della posizione e di un colore per il bordo, ci sono due cose sulle quali dobbiamo soffermarci. Poichè visualizzeremo i dati contenuti in una collection, attraverso IsSynchronizedWithCurrentItem ci assicuriamo che oggetto della collection ed elemento della ListView siano sincronizzati. ItemsSource ci permette di stabilire, tramite data-binding, quale oggetto costituisce la fonte dati per gli elementi della ListView. Non abbiamo specificato alcun oggetto attualmente, perché lo faremo poi da codice Visual Basic, però stiamo dicendo a WPF che la proprietà è alimentata tramite data-binding (in sostanza gli imponiamo di tenere il binding in sospeso, finchè non gli passeremo la sorgente).

Fermiamoci per un momento e ragioniamo sui dati da mostrare/modificare. Ciascun prodotto che mostreremo gode di alcune proprietà. Per semplicità, ne mostreremo solo tre: ProductID (di tipo Integer), Discontinued (di tipo Boolean) e ProductName (di tipo String). Vogliamo, poi, che i dati siano visualizzati e modificati in modalità tabulare, quindi sfruttando una griglia. In attesa che venga rilasciato in versione definitiva il WPF Toolkit (un progetto attualmente su CodePlex), in cui sarà disponibile un controllo DataGrid per WPF, dobbiamo considerare che allo stato attuale non c'è un controllo simile alla DataGridView di Windows Forms, quindi dobbiamo costruircelo da soli. In realtà, questa è un'operazione molto semplice, poiché ci basta modificare il layout della ListView. Innanzitutto si imposta la modalità di visualizzazione, dicendo a WPF che la ListView deve avere l'aspetto di una griglia:

            <ListView.View>
                <GridView>

La griglia è, per forza di cose, suddivisa in colonne. Ogni colonna è identificata da un elemento GridViewColumn che si definisce così:

                    <GridViewColumn Header="Product ID" Width="80">

La proprietà Header stabilisce il testo dell'intestazione, mentre Width la larghezza della colonna. Ora, ogni colonna visualizza i dati in singole celle. Per ogni cella deve essere stabilito un template a cui essa deve conformarsi, esattamente come facemmo nel precedente articolo, in cui stabilimmo un template per gli elementi della ListView. Anche in questo caso faremo uso di un DataTemplate. Per visualizzare il ProductID, chiudiamo il codice appena iniziato come segue:

                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBox Text="{Binding Path=ProductID}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

La nostra cella mostrerà il ProductID attraverso una TextBox modificabile (invece di un TextBlock a sola lettura come la volta scorsa) e la sua proprietà Text è alimentata, tramite binding, dal valore della proprietà ProductID di ciascun prodotto che sarà assegnato a run-time. I DataTemplate, in WPF, sono molto flessibili. Considerate, infatti, la colonna successiva relativa alla proprietà Discontinued di ciascun prodotto. Essendo questa di tipo booleano, potrebbe essere opportuno visualizzarne il valore attraverso una CheckBox. Il codice è il seguente:

                    <GridViewColumn Header="Discontinued" Width="80">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox IsChecked="{Binding Path=Discontinued}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

Qual è quindi la nostra deduzione successiva? Semplice: WPF traduce il valore True o False della proprietà Discontinued nel corrispondente stato della CheckBox. La considerazione seguente è che, con WPF, possiamo mostrare qualunque tipo di dato nel modo che riteniamo più opportuno (si pensi, per esempio, a una fotografia da mostrare in un controllo Image costituente parte del DataTemplate).

Da ultimo, scriviamo il codice relativo alla terza colonna, per visualizzare e modificare il nome del prodotto:

                    <GridViewColumn Header="Product Name" Width="200">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBox Text="{Binding Path=ProductName}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>

Se avete già avuto modo di utilizzare il controllo ListView in modalità GridView, probabilmente sapete già che i controlli costituenti il DataTemplate adattano le loro dimensioni in base al loro contenuto, con scarsi risultati estetici. Per uniformare il tutto alla stessa larghezza delle colonne, scriviamo quanto segue:

            <!--Modalità di visualizzazione di ciascun elemento-->
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                    <EventSetter Event="GotFocus" Handler="Item_GotFocus"/>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>

L'ItemContainerStyle permette di definire l'aspetto estetico di ciascun elemento della ListView. Impostando la proprietà HorizontalContentAlignment come Stretch, ci assicuriamo che ciascun elemento sia largo quanto l'header della colonna. È interessante notare la specificazione di un gestore per l'evento GotFocus. Infatti, potrebbe capitare di rimuovere la riga sbagliata nonostante la riga selezionata sia quella desiderata. Gestendo il focus, ci assicuriamo che tutto viaggi di pari passo evitando errori.

A questo punto possiamo implementare i pulsanti. In particolare, due ci serviranno per andare avanti e indietro con la paginazione dei dati visualizzati e tre ci serviranno per le operazioni di aggiunta, rimozione e salvataggio dei dati. Secondo una separazione logica delle categorie di pulsanti, possiamo aggiungere uno StackPanel al cui interno insistano altrettanti contenitori per i pulsanti specificati. Il codice è estremamente semplice. Questo riguarda l'aggiunta dei primi due pulsanti:

        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3" >
                <StackPanel Orientation="Horizontal">
                    <Button Name="PrevButton" Width="50" Height="20" Click="PrevButton_Click" 
                            Margin="5" Content="Previous"  />
                    <Button Name="NextButton" Width="50" Height="20" Click="NextButton_Click" 
                            Margin="5" Content="Next"/>
                </StackPanel>
            </Border>

I rimanenti pulsanti vengono poi disegnati nel modo seguente:

            <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3" Width="Auto" >
                <StackPanel Orientation="Horizontal">
                    <Button Name="AddButton" Width="50" Height="20" Click="AddButton_Click" 
                            Margin="5" Content="Add"/>

                    <Button Name="DeleteButton" Width="50" Height="20" Click="DeleteButton_Click" 
                            Margin="5" Content="Delete"/>

                    <Button Name="SaveButton" Width="50" Height="20" Click="SaveButton_Click" 
                            Margin="5" Content="Save"/>
                </StackPanel>
            </Border>
        </StackPanel>
    </Grid>
</Window>

Dopo questa lunga serie di operazioni, il designer di Visual Studio si presenta come in figura:

L'estetica è piuttosto essenziale, ma non è questo lo scopo dell'articolo. Ci interessa, infatti, capire come lavorare sui dati. Potete poi mettere a frutto le conoscenze acquisite sugli stili e i template per modificare e migliorare l'aspetto dell'applicazione. Dopo aver definito l'interfaccia, ci buttiamo a capofitto nel codice Visual Basic "operativo" grazie al quale scopriremo molto utili oggetti.

Il codice Visual Basic per lavorare coi dati
Nella finestra Solution Explorer, fate doppio clic sul file Window1.xaml.vb per attivare l'editor di codice Visual Basic. A livello di classe, dichiarate i seguenti oggetti:

    'Ottiene l'istanza dell'ObjectContext
    Private northwind As New NORTHWNDEntities

    'Dichiara una BindingListCollectionView, che consente
    'di lavorare su oggetti provenienti da ADO.NET
    Private ProductView As BindingListCollectionView

    'un campo privato per tenere traccia della paginazione
    Private pageCount As Integer = 0

NORTHWNDEntities è l'oggetto che eredita da ObjectContext e la sua istanza Northwind ci permette di lavorare in forma gestita con gli oggetti mappati dall'Entity Framework. ProductView è un oggetto di tipo BindingListCollectionView. Tra le varie collezioni disponibili in WPF, questa è specifica per lavorare con dati provenienti da ADO.NET (e gli strati superiori come l'Entity Framework). È molto utile come collection, poiché offre il supporto per il data-binding di tipo Two-way. L'ultimo oggetto, PageCount, è un intero che ci servirà per tenere traccia della paginazione. Scriviamo ora un metodo che ci permetta di estrarre i dati inerenti i nostri prodotti dall'entità Products, stabilendo di voler visualizzare dieci prodotti alla volta nella nostra ListView. Noterete subito come la sintassi di LINQ rimanga la medesima anche in LINQ-to-Entities:

    Private Function queryCustomers() As IQueryable(Of Products)

        'Se pageCount è = 0 vuol dire che bisogna recuperare i primi 10
        'prodotti
        If pageCount = 0 Then
            Dim query = From product In northwind.Products _
                        Order By product.ProductID _
            Take 10

            Return query
        Else
            'diversamente, escludiamo quelli visualizzati in precedenza
            'e passiamo ai 10 successivi
            Dim query = From product In northwind.Products _
                        Order By product.ProductID _
                        Skip pageCount Take 10

            Return query
        End If
    End Function

Come in LINQ-to-SQL, anche in LINQ-to-Entities il risultato delle query produce un tipo generico IQueryable. Sfruttiamo il valore della variabile PageCount per determinare quanti prodotti abbiamo già visualizzato. Se PageCount è uguale a zero, bisogna visualizzare i primi dieci. Diversamente, si escludono (clausola Skip) tutti quelli già visualizzati e si passa (Take) ai successivi dieci. Il valore di PageCount verrà poi manipolato dai gestori di evento Click dei due pulsanti per lo spostamento, come vedremo più avanti.

Una nota molto importante da fare con riferimento a questo codice è che, in LINQ-to-Entities, è obbligatorio utilizzare una clausola Order By laddove si abbia necessità di ricorrere alla Skip. In caso contrario, il run-time solleva un'eccezione. Notate come la nostra query, nella fase iniziale dell'applicazione (quindi con PageCount = 0), ottenga una collezione di tutti i prodotti. Questo perchè il risultato così ottenuto costituirà la fonte dati da utilizzare nel binding. A tal proposito, consideriamo di riscrivere il costruttore come segue:

    Public Sub New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        'Assegna la sorgente dati alla finestra, a livello globale
        Me.DataContext = queryCustomers()
        ProductView = CType(CollectionViewSource.GetDefaultView(Me.DataContext), _
                            BindingListCollectionView)
    End Sub

Il risultato del metodo QueryCustomers è assegnato alla proprietà DataContext dell'oggetto Window. Così facendo stabiliamo che la sorgente dati su cui l'applicazione lavorerà è una collezione di Products. Nella riga successiva, il metodo GetDefaultView della classe CollectionViewSource converte proprio il DataContext (quindi il risultato della query) in un oggetto di tipo BindingListCollectionView che viene assegnato all'oggetto ProductView precedentemente dichiarato. Questa operazione farà sì che ProductView alimenterà la ListView (sebbene non ci sia un'associazione esplicita tra i due, l'aver assegnato la sorgente dati al DataContext della Window ha come conseguenza che il controllo dipendente si alimenti proprio da lì).

Scriviamo ora il codice inerente i due pulsanti che utilizzeremo per paginare i dati:

    Private Sub PrevButton_Click(ByVal sender As System.Object, _
                                 ByVal e As System.Windows.RoutedEventArgs)
        'sottrae 10 unità al conto al fine di tornare alla pagina precedente
        pageCount -= 10

        'se però siamo già alla prima pagina
        If pageCount < 0 Then
            pageCount = 0       'riporta a zero il conto ed esce

            Exit Sub
        End If

        'e mostra il risultato della query
        ProductsListView.ItemsSource = queryCustomers()

        'Ottengo la collezione di oggetti attualmente visualizzata
        ProductView = CType(CollectionViewSource.GetDefaultView(ProductsListView.ItemsSource), _
                            BindingListCollectionView)
    End Sub

    Private Sub NextButton_Click(ByVal sender As System.Object, _
                                 ByVal e As System.Windows.RoutedEventArgs)
        'Aumenta di 10 unità il conto al fine di andare alla pagina successiva
        pageCount += 10

        'Se il conto è superiore al totale dei prodotti, esce
        If pageCount >= northwind.Products.Count Then Exit Sub

        'Mostra il risultato della query
        ProductsListView.ItemsSource = queryCustomers()
        ProductView = CType(CollectionViewSource.GetDefaultView(ProductsListView.ItemsSource), _
                            BindingListCollectionView)
    End Sub

I commenti nel codice aiutano a capire le semplici operazioni per il calcolo degli elementi da visualizzare. Quello che è importante in questa sede è l'ultima riga di ciascun metodo. Stiamo praticamente aggiornando la collezione ProductView popolandola con gli elementi visualizzati nella ListView. Questo ci occorre per poter mantenere l'associazione tra la ListView e la collezione che gestisce i suoi elementi, in modo da poter essere in grado di eseguire operazioni CRUD (Create, Read, Update, Delete) anche a seguito della paginazione (che farebbe perdere l'associazione tra i due oggetti). È un po' quello che in Windows Forms faremmo aggiornando una BindingSource.

Scriviamo ora il gestore di evento relativo al pulsante di aggiunta dati:

    Private Sub AddButton_Click(ByVal sender As System.Object, _
                                ByVal e As System.Windows.RoutedEventArgs)

        Dim product = CType(Me.ProductView.AddNew, Products)
        product.ProductName = "Test product"
        Me.ProductView.CommitNew()

    End Sub

I metodi AddNew e CommitNew sono nuovi nel Service Pack 1 per .NET 3.5 e permettono, rispettivamente, di istanziare un nuovo oggetto del tipo desiderato e aggiungerlo alla collection. Il nuovo elemento viene aggiunto alla fine di quelli visualizzati. In questo caso assegnamo un valore di default (un testo standard) affinché venga visualizzato qualcosa nel nuovo prodotto.
Ora è il turno del metodo per la rimozione di un record:

    Private Sub DeleteButton_Click(ByVal sender As System.Object, _
                                   ByVal e As System.Windows.RoutedEventArgs)
        If Me.ProductView.CurrentPosition > -1 Then
            Me.ProductView.RemoveAt(Me.ProductView.CurrentPosition)
        End If
    End Sub

Tramite il metodo RemoveAt possiamo eliminare dalla collezione l'elemento selezionato nella ListView. Qui potete capire meglio il perché dell'attributo IsSynchronizedWithCurrentItem della ListView: stiamo infatti lavorando sulla collezione associata, ma non sul controllo, che tiene traccia automaticamente della posizione proprio grazie a quell'attributo.
Questo, invece, è il metodo per il salvataggio delle modifiche:

    Private Sub SaveButton_Click(ByVal sender As System.Object, _
                                 ByVal e As System.Windows.RoutedEventArgs)
        Try
            northwind.SaveChanges()
        Catch ex As Exception
            MessageBox.Show(ex.ToString)
        End Try
    End Sub

La classe ObjectContext espone il metodo SaveChanges (corrispondente al SubmitChanges di LINQ-to-SQL) che invia le modifiche al database.
L'ultimo passo riguarda il metodo per gestire la ricezione del focus da parte di ciascun ListViewItem:

    Private Sub Item_GotFocus(ByVal sender As System.Object, _
                              ByVal e As System.Windows.RoutedEventArgs)
        Dim item = CType(sender, ListViewItem)
        Me.ProductsListView.SelectedItem = item.DataContext
    End Sub

Abbiamo così completato la scrittura di tutto il codice che ci occorre e siamo pronti per eseguire e testare la nostra applicazione.

Esecuzione dell'applicazione e modifica dei dati
Premete F5 per mandare in esecuzione l'applicazione che, se non ci sono problemi di sorta, si presenta come in figura:

Ora provate a fare clic sui pulsanti Next e Previous per verificare la corretta paginazione dei dati. Per esempio, visualizzate i prodotti dal numero 31 al 40 e apponete un segno di spunta nella cella Discontinued relativa al prodotto Mascarpone Fabioli, come in figura:

A questo punto fate clic sul pulsante Add e popolate il nuovo prodotto come preferite, quindi fate clic su Save. In figura c'è un esempio:

Per verificare che il nuovo prodotto sia stato effettivamente aggiunto al database e che quello precedente sia stato aggiornato con le modifiche, chiudete e riavviate l'applicazione. Il nuovo prodotto verrà visualizzato in fondo all'elenco dei prodotti. Per rimuoverlo, vi basterà utilizzare il pulsante Delete e poi ancora Save.

Risorse utili

Conclusioni
In questo articolo avete imparato molti concetti importanti sia sull'ADO.NET Entity Framework che su WPF e avete visto come sia essenzialmente semplice creare una griglia per l'editing dei dati e il successivo invio alla sorgente.
Per ulteriori informazioni potete contattarmi al mio indirizzo visitare il mio blog.