Condividi tramite


Creare un'applicazione dati semplice con WPF ed Entity Framework 6

Avvertimento

Se si usa Visual Studio 2022, è consigliabile usare Visual Studio 2022 versione 17.3 Preview 3 o successiva per questa esercitazione.

Questa procedura dettagliata illustra come creare un'applicazione di base "moduli su dati" in Visual Studio. L'app usa SQL Server LocalDB, il database Northwind, Entity Framework 6 (non Entity Framework Core) e Windows Presentation Foundation per .NET Framework (non .NET Core o .NET 5 o versione successiva). Illustra come eseguire l'associazione dati di base con una visualizzazione master-dettagli e include anche un navigatore di associazione dati personalizzato con pulsanti per Sposta successivo, Sposta precedente, Sposta all'inizio, Sposta alla fine, Aggiorna e Elimina.

Questo articolo è incentrato sull'uso degli strumenti di dati in Visual Studio e non tenta di spiegare le tecnologie sottostanti in modo approfondito. Si presuppone che si abbia una conoscenza di base di XAML, Entity Framework e SQL. Questo esempio non illustra anche l'architettura MVVM (Model-View-ViewModel), standard per le applicazioni WPF. Tuttavia, è possibile copiare questo codice nella propria applicazione MVVM con poche modifiche.

Il codice finale per questa esercitazione è disponibile in GitHub in Visual Studio Tutorial Samples - EF6.

Installare e connettersi a Northwind

Questo esempio usa SQL Server Express LocalDB e il database di esempio Northwind. Se il provider di dati ADO.NET per tale prodotto supporta Entity Framework, dovrebbe funzionare anche con altri prodotti di database SQL.

  1. Se SQL Server Express LocalDB non è disponibile, installarlo tramite il programma di installazione di Visual Studio . Nel programma di installazione di Visual Studioè possibile installare SQL Server Express LocalDB come parte del carico di lavoro di archiviazione ed elaborazione dei dati o come singolo componente.

  2. Installare il database di esempio Northwind seguendo questa procedura:

    1. In Visual Studio, aprire la finestra Esplora oggetti di SQL Server. (Esplora oggetti di SQL Server viene installato come parte del carico di lavoro di archiviazione ed elaborazione dei dati nel programma di installazione di Visual Studio .) Espandi il nodo di SQL Server. Fare clic con il pulsante destro del mouse sull'istanza di LocalDB e selezionare Nuova query.

      Verrà visualizzata una finestra dell'editor di query.

    2. Copia lo script Northwind Transact-SQL negli Appunti. Questo script T-SQL crea il database Northwind da zero e lo popola con i dati.

    3. Incolla lo script T-SQL nell'editor di query e poi scegli il pulsante Esegui.

      Dopo un breve periodo di tempo, la query termina l'esecuzione e viene creato il database Northwind.

  3. Aggiungere nuove connessioni per Northwind.

Configurare il progetto

  1. In Visual Studio, crea un nuovo progetto di app WPF C# (.NET Framework) .

  2. Aggiungere il pacchetto NuGet per Entity Framework 6. In Esplora soluzioniselezionare il nodo del progetto. Nel menu principale scegliere Progetto>Gestisci pacchetti NuGet.

  3. Nel Gestore pacchetti NuGet, fare clic sul collegamento Sfoglia. Entity Framework è probabilmente il pacchetto principale nell'elenco. Fare clic su Installa nel riquadro destro e seguire le istruzioni. La finestra Output indica quando l'installazione è terminata.

    screenshot del pacchetto NuGet di Entity Framework.

    Screenshot che mostra il pacchetto NuGet di Entity Framework.

  4. È ora possibile usare Visual Studio per creare un modello basato sul database Northwind.

Creare il modello

  1. Fare clic con il pulsante destro del mouse sul nodo del progetto in esplora soluzioni e scegliere Aggiungi>Nuovo elemento. Nel riquadro sinistro, sotto il nodo C#, scegliere Data e nel riquadro centrale scegliere ADO.NET Entity Data Model.

    screenshot del nuovo elemento del modello Entity Framework.

    screenshot del nuovo elemento del modello Entity Framework.

  2. Chiama il modello Northwind_model e scegli Aggiungi. Si apre la Creazione guidata del modello dati entity. Scegliere EF Designer dal databasee quindi selezionare Avanti.

    screenshot del modello ef dal database.

  3. Nella schermata successiva scegliere la connessione Northwind localDB ,ad esempio (localdb)\MSSQLLocalDB, specificare il database Northwind e fare clic su Avanti.

    Se non viene visualizzata una connessione, scegliere Nuova connessione, quindi nella finestra di dialogo Scegliere origine dati scegliere Microsoft SQL Server, scegliere Continua e nella finestra di dialogo Proprietà connessione immettere (localdb)\MSSQLLocalDB e in Selezionare o immettere un nome di databasescegliere Northwind, , quindi premere OK.

  4. Se richiesto, scegliere la versione di Entity Framework in uso.

    Screenshot che mostra le opzioni di versione.

  5. Nella pagina successiva della procedura guidata scegliere quali tabelle, stored procedure e altri oggetti di database includere nel modello Entity Framework. Espandi il nodo dbo nella visualizzazione ad albero e scegli Clienti, Ordinie Dettagli Ordini. Lasciare selezionate le impostazioni predefinite e fare clic sul pulsante Fine.

    Screenshot della scelta di oggetti di database per il modello.

  6. La procedura guidata genera le classi C# che rappresentano il modello Entity Framework. Le classi sono semplici classi C# standard e sono quelle a cui associamo i dati all'interfaccia utente WPF. Il file .edmx descrive le relazioni e altri metadati che associano le classi agli oggetti nel database. I file .tt sono modelli T4 che generano il codice che opera sul modello e salvano le modifiche nel database. È possibile visualizzare tutti questi file in Esplora soluzioni nel nodo Northwind_model:

    Screenshot che mostra i file del modello di Entity Framework in Esplora Soluzioni.

    Screenshot che mostra i file del modello Entity Framework di Esplora Soluzioni

    L'area di progettazione per il file .edmx consente di modificare alcune proprietà e relazioni nel modello. In questa procedura dettagliata non useremo il designer.

  7. I file .tt sono per utilizzo generico ed è necessario modificarli per lavorare con il databinding WPF, che richiede ObservableCollections. In Esplora soluzioni, espandere il nodo Northwind_model fino a trovare Northwind_model.tt. Assicurarsi di non essere nel file Context.tt, che si trova direttamente sotto il file .edmx.

  8. Premere F5 o CTRL+F5 per compilare ed eseguire il progetto. Quando l'applicazione viene eseguita per la prima volta, le classi del modello sono visibili alla procedura guidata origini dati.

Ora sei pronto per associare questo modello alla pagina XAML in modo da poter visualizzare, esplorare e modificare i dati.

Associa il modello alla pagina XAML tramite data binding

È possibile scrivere codice di associazione dati personalizzato, ma è molto più semplice consentire a Visual Studio di farlo automaticamente.

  1. Dal menu principale, scegliere Progetto>per aggiungere una nuova origine dati e visualizzare la Configurazione guidata dell'origine dati . Scegliere Object perché si esegue l'associazione alle classi del modello, non al database:

    Screenshot della Procedura guidata per la configurazione dell'origine dati con fonte oggetto.

  2. Espandi il nodo per il tuo progetto e seleziona Customer. Le fonti per gli ordini vengono generate automaticamente dalla proprietà di navigazione "Orders" nel cliente.

    Screenshot che mostra l'aggiunta di classi di entità come origini dati.

    Screenshot che mostra l'aggiunta di classi di entità come origini dati.

  3. Fare clic su Fine.

  4. Passare a MainWindow.xaml in Visualizzazione codice. Il codice XAML viene mantenuto semplice ai fini di questo esempio. Modificare il titolo di MainWindow in un elemento più descrittivo e aumentarne altezza e larghezza a 600 x 800 per il momento. È sempre possibile modificarlo in un secondo momento. Aggiungere ora queste tre definizioni di riga alla griglia principale, una riga per i pulsanti di spostamento, una per i dettagli del cliente e una per la griglia che mostra gli ordini:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. Aprire ora MainWindow.xaml in modo da visualizzarlo nella finestra di progettazione. In questo modo, la finestra origini dati viene visualizzata come opzione nel margine della finestra di Visual Studio accanto alla casella degli strumenti . Fare clic sulla scheda per aprire la finestra, oppure premere Maiusc+Alt+D oppure scegliere Visualizza>Altre finestre>Origini dati. Visualizzeremo ogni proprietà della classe Customers in una singola casella di testo dedicata. Per prima cosa, cliccare sulla freccia nella casella combinata Clienti e scegliere Dettagli. Quindi, trascina il nodo nella parte centrale della superficie di progettazione affinché il programma comprenda che desideri posizionarlo nella sezione centrale. Se non lo si trova, è possibile specificare manualmente la riga in un secondo momento in XAML (Grid.Row="1"). Per impostazione predefinita, i controlli vengono posizionati verticalmente in un elemento della griglia, ma a questo punto è possibile disporre i controlli come nel form. Ad esempio, potrebbe essere opportuno inserire la casella di testo nome sopra l'indirizzo. L'applicazione di esempio per questo articolo riordina i campi e li riorganizzerà in due colonne.

    Screenshot che mostra il binding dell'origine dati Clienti a dei singoli controlli.

    Screenshot che mostra come l'origine dati clienti venga collegata a singoli controlli.

    Nella visualizzazione XAML è ora possibile visualizzare un nuovo elemento Grid nella riga 1 (riga centrale) della griglia padre. Grid padre ha un attributo DataContext che fa riferimento a un CollectionViewSource aggiunto all'elemento Windows.Resources. Dato il contesto dei dati, quando la prima casella di testo è associata a Address, quel nome viene mappato alla proprietà Address nell'oggetto Customer corrente nel CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. Quando un cliente è visibile nella metà superiore della finestra, si vuole visualizzare gli ordini nella metà inferiore. Gli ordini sono visualizzati in un controllo di visualizzazione a griglia unica. Per il corretto funzionamento del collegamento dati master-dettaglio, è importante che tu colleghi alla proprietà Orders nella classe Customers e non al nodo Orders, che è separato. Trascinare la proprietà Orders della classe Customers nella metà inferiore del modulo, in modo che il progettista la inserisca nella riga 2:

    Screenshot che mostra le classi di ordini trascinate e rilasciate come griglia.

    Screenshot che mostra le classi Orders trascinate e rilasciate come griglia.

  7. Visual Studio ha generato tutto il codice di associazione che connette i controlli dell'interfaccia utente agli eventi nel modello. Per visualizzare alcuni dati, è sufficiente scrivere codice per popolare il modello. Prima, navigare a MainWindow.xaml.cs e aggiungere un membro dati alla classe MainWindow per il contesto dei dati. Questo oggetto, generato automaticamente, agisce come un controllo che tiene traccia delle modifiche e degli eventi nel modello. Aggiungerai anche membri di dati CollectionViewSource per clienti e ordini, e la logica di inizializzazione del costruttore associata per il costruttore esistente MainWindow(). I migliori della classe dovrebbero essere simili a questo:

    public partial class MainWindow : Window
    {
        NorthwindEntities context = new NorthwindEntities();
        CollectionViewSource custViewSource;
        CollectionViewSource ordViewSource;
    
        public MainWindow()
        {
            InitializeComponent();
            custViewSource = ((CollectionViewSource)(FindResource("customerViewSource")));
            ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource")));
            DataContext = this;
        }
    

    Se non è già presente, aggiungere una direttiva using per System.Data.Entity per portare il metodo di estensione Load nell'ambito:

    using System.Data.Entity;
    

    Scorrere verso il basso e trovare il gestore eventi Window_Loaded. Si noti che Visual Studio ha aggiunto un oggetto CollectionViewSource. Rappresenta l'oggetto NorthwindEntities selezionato al momento della creazione del modello. L'hai già aggiunto, quindi non serve qui. Sostituire il codice in Window_Loaded in modo che il metodo sia ora simile al seguente:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Load is an extension method on IQueryable,    
        // defined in the System.Data.Entity namespace.   
        // This method enumerates the results of the query,    
        // similar to ToList but without creating a list.   
        // When used with Linq to Entities, this method    
        // creates entity objects and adds them to the context.   
        context.Customers.Load();
    
        // After the data is loaded, call the DbSet<T>.Local property    
        // to use the DbSet<T> as a binding source.   
        custViewSource.Source = context.Customers.Local;
    }
    
  8. Premere F5. Verranno visualizzati i dettagli per il primo cliente recuperato in CollectionViewSource. Verranno visualizzati anche gli ordini nella griglia dei dati. La formattazione non è ottimale, quindi è possibile correggerlo. È anche possibile creare un modo per visualizzare gli altri record ed eseguire operazioni di base per la creazione, la lettura, l'aggiornamento e l'eliminazione (CRUD).

Modificare la progettazione della pagina e aggiungere griglie per nuovi clienti e ordini

La disposizione predefinita prodotta da Visual Studio non è ideale per l'applicazione, quindi verrà fornito il codice XAML finale da copiare nel codice. Sono necessari anche alcuni "moduli" (che sono effettivamente Griglie) per consentire all'utente di aggiungere un nuovo cliente o un nuovo ordine. Per poter aggiungere un nuovo cliente e un nuovo ordine, è necessario un set separato di caselle di testo non associate al CollectionViewSource. È possibile controllare la griglia visualizzata dall'utente in qualsiasi momento impostando la proprietà Visible nei metodi del gestore. Infine, si aggiunge un pulsante Elimina a ogni riga della griglia Ordini per consentire all'utente di eliminare un singolo ordine.

Aggiungere prima di tutto questi stili all'elemento Windows.Resources in MainWindow.xaml:

<Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}">
    <Setter Property="HorizontalAlignment" Value="Left"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="Margin" Value="3"/>
    <Setter Property="Height" Value="23"/>
</Style>
<Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}">
    <Setter Property="HorizontalAlignment" Value="Right"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="Margin" Value="3"/>
    <Setter Property="Height" Value="26"/>
    <Setter Property="Width" Value="120"/>
</Style>

Successivamente, sostituire l'intera griglia esterna con questo markup:

<Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="auto"/>
         <RowDefinition Height="auto"/>
         <RowDefinition Height="*"/>
     </Grid.RowDefinitions>
     <Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
         <TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
         <TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/>
         <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="New Order Form" FontWeight="Bold"/>
         <Label Content="Employee ID:"  Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Order Date:"  Grid.Row="2" Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_orderDatePicker" Grid.Row="2"  HorizontalAlignment="Right" Width="120"
                 SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Shipped Date:"  Grid.Row="4"  Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_shippedDatePicker"  Grid.Row="4"  HorizontalAlignment="Right" Width="120"
                 SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Ship Via:"  Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_ShipViaTextBox"  Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Freight"  Grid.Row="6" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}"
                  Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected">
         <DataGrid.Columns>
             <DataGridTemplateColumn>
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/>
         </DataGrid.Columns>
     </DataGrid>
 </Grid>

Aggiungere pulsanti per spostarsi, aggiungere, aggiornare ed eliminare

Nelle applicazioni Windows Form si ottiene un oggetto BindingNavigator con pulsanti per spostarsi tra righe in un database ed eseguire operazioni CRUD di base. WPF non fornisce bindingNavigator, ma è abbastanza semplice crearne uno. Lo fai con i pulsanti all'interno di uno StackPanel orizzontale e associ i pulsanti ai comandi che sono collegati ai metodi nel codice sottostante.

Esistono quattro parti per la logica del comando: (1) i comandi, (2) i binding, (3) i pulsanti e (4) i gestori dei comandi nel code-behind.

Aggiungere comandi, associazioni e pulsanti in XAML

  1. Prima di tutto, aggiungere i comandi nel file MainWindow.xaml all'interno dell'elemento Windows.Resources:

    <RoutedUICommand x:Key="FirstCommand" Text="First"/>
    <RoutedUICommand x:Key="LastCommand" Text="Last"/>
    <RoutedUICommand x:Key="NextCommand" Text="Next"/>
    <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/>
    <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/>
    <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/>
    <RoutedUICommand x:Key="UpdateCommand" Text="Update"/>
    <RoutedUICommand x:Key="AddCommand" Text="Add"/>
    <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
    
  2. CommandBinding mappa un evento RoutedUICommand a un metodo nel code-behind. Aggiungere questo elemento CommandBindings dopo il tag di chiusura Windows.Resources:

    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/>
        <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/>
        <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/>
        <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/>
        <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/>
        <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/>
        <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/>
    </Window.CommandBindings>
    
  3. Ora, aggiungi il StackPanel con i pulsanti di navigazione, aggiungi, elimina e aggiorna. Aggiungere prima di tutto questo stile a Windows.Resources:

    <Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="FontFamily" Value="Segoe UI Symbol"/>
        <Setter Property="Margin" Value="2,2,2,0"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="auto"/>
    </Style>
    

    In secondo luogo, incollare questo codice subito dopo il RowDefinitions per l'elemento Grid esterno, verso la parte superiore della pagina XAML:

    <StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin">
        <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/>
        <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
        <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/>
        <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
        <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
    </StackPanel>
    

Aggiungere gestori di comandi alla classe MainWindow

Il codice associato è minimo, ad eccezione dei metodi di aggiunta ed eliminazione. La navigazione viene eseguita chiamando i metodi nella proprietà View di CollectionViewSource. Il DeleteOrderCommandHandler mostra come eseguire un'eliminazione a catena in un ordine. È necessario prima eliminare il Order_Details associato. Il UpdateCommandHandler aggiunge un nuovo cliente o ordine alla raccolta oppure aggiorna semplicemente un cliente o un ordine esistente con le modifiche apportate dall'utente nelle caselle di testo.

Aggiungere questi metodi del gestore alla classe MainWindow in MainWindow.xaml.cs. Se la tabella CollectionViewSource per la tabella Customers ha un nome diverso, è necessario modificare il nome in ognuno di questi metodi:

private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToLast();
}

private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToPrevious();
}

private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToNext();
}

private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToFirst();
}

private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    // If existing window is visible, delete the customer and all their orders.  
    // In a real application, you should add warnings and allow the user to cancel the operation.  
    var cur = custViewSource.View.CurrentItem as Customer;

    var cust = (from c in context.Customers
                where c.CustomerID == cur.CustomerID
                select c).FirstOrDefault();

    if (cust != null)
    {
        foreach (var ord in cust.Orders.ToList())
        {
            Delete_Order(ord);
        }
        context.Customers.Remove(cust);
    }
    context.SaveChanges();
    custViewSource.View.Refresh();
}

// Commit changes from the new customer form, the new order form,  
// or edits made to the existing customer form.  
private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    if (newCustomerGrid.IsVisible)
    {
        // Create a new object because the old one  
        // is being tracked by EF now.  
        Customer newCustomer = new Customer
        {
            Address = add_addressTextBox.Text,
            City = add_cityTextBox.Text,
            CompanyName = add_companyNameTextBox.Text,
            ContactName = add_contactNameTextBox.Text,
            ContactTitle = add_contactTitleTextBox.Text,
            Country = add_countryTextBox.Text,
            CustomerID = add_customerIDTextBox.Text,
            Fax = add_faxTextBox.Text,
            Phone = add_phoneTextBox.Text,
            PostalCode = add_postalCodeTextBox.Text,
            Region = add_regionTextBox.Text
        };

        // Perform very basic validation  
        if (newCustomer.CustomerID.Length == 5)
        {
            // Insert the new customer at correct position:  
            int len = context.Customers.Local.Count();
            int pos = len;
            for (int i = 0; i < len; ++i)
            {
                if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0)
                {
                    pos = i;
                    break;
                }
            }
            context.Customers.Local.Insert(pos, newCustomer);
            custViewSource.View.Refresh();
            custViewSource.View.MoveCurrentTo(newCustomer);
        }
        else
        {
            MessageBox.Show("CustomerID must have 5 characters.");
        }

        newCustomerGrid.Visibility = Visibility.Collapsed;
        existingCustomerGrid.Visibility = Visibility.Visible;
    }
    else if (newOrderGrid.IsVisible)
    {
        // Order ID is auto-generated so we don't set it here.  
        // For CustomerID, address, etc we use the values from current customer.  
        // User can modify these in the datagrid after the order is entered.  

        Customer currentCustomer = (Customer)custViewSource.View.CurrentItem;

        Order newOrder = new Order()
        {
            OrderDate = add_orderDatePicker.SelectedDate,
            RequiredDate = add_requiredDatePicker.SelectedDate,
            ShippedDate = add_shippedDatePicker.SelectedDate,
            CustomerID = currentCustomer.CustomerID,
            ShipAddress = currentCustomer.Address,
            ShipCity = currentCustomer.City,
            ShipCountry = currentCustomer.Country,
            ShipName = currentCustomer.CompanyName,
            ShipPostalCode = currentCustomer.PostalCode,
            ShipRegion = currentCustomer.Region
        };

        try
        {
            newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text);
        }
        catch
        {
            MessageBox.Show("EmployeeID must be a valid integer value.");
            return;
        }

        try
        {
            // Exercise for the reader if you are using Northwind:  
            // Add the Northwind Shippers table to the model.
            
            // Acceptable ShipperID values are 1, 2, or 3.  
            if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2"
                || add_ShipViaTextBox.Text == "3")
            {
                newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text);
            }
            else
            {
                MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind.");
                return;
            }
        }
        catch
        {
            MessageBox.Show("Ship Via must be convertible to int");
            return;
        }

        try
        {
            newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text);
        }
        catch
        {
            MessageBox.Show("Freight must be convertible to decimal.");
            return;
        }

        // Add the order into the EF model  
        context.Orders.Add(newOrder);
        ordViewSource.View.Refresh();
    }

    // Save the changes, either for a new customer, a new order  
    // or an edit to an existing customer or order.
    context.SaveChanges();
}

// Sets up the form so that user can enter data. Data is later  
// saved when user clicks Commit.  
private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    existingCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.Visibility = Visibility.Collapsed;
    newCustomerGrid.Visibility = Visibility.Visible;

    // Clear all the text boxes before adding a new customer.  
    foreach (var child in newCustomerGrid.Children)
    {
        var tb = child as TextBox;
        if (tb != null)
        {
            tb.Text = "";
        }
    }
}

private void NewOrder_click(object sender, RoutedEventArgs e)
{
    var cust = custViewSource.View.CurrentItem as Customer;
    if (cust == null)
    {
        MessageBox.Show("No customer selected.");
        return;
    }

    existingCustomerGrid.Visibility = Visibility.Collapsed;
    newCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.UpdateLayout();
    newOrderGrid.Visibility = Visibility.Visible;
}

// Cancels any input into the new customer form  
private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    add_addressTextBox.Text = "";
    add_cityTextBox.Text = "";
    add_companyNameTextBox.Text = "";
    add_contactNameTextBox.Text = "";
    add_contactTitleTextBox.Text = "";
    add_countryTextBox.Text = "";
    add_customerIDTextBox.Text = "";
    add_faxTextBox.Text = "";
    add_phoneTextBox.Text = "";
    add_postalCodeTextBox.Text = "";
    add_regionTextBox.Text = "";

    existingCustomerGrid.Visibility = Visibility.Visible;
    newCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.Visibility = Visibility.Collapsed;
}

private void Delete_Order(Order order)
{
    // Find the order in the EF model.  
    var ord = (from o in context.Orders.Local
               where o.OrderID == order.OrderID
               select o).FirstOrDefault();

    // Delete all the order_details that have  
    // this Order as a foreign key  
    foreach (var detail in ord.Order_Details.ToList())
    {
        context.Order_Details.Remove(detail);
    }

    // Now it's safe to delete the order.  
    context.Orders.Remove(ord);
    context.SaveChanges();

    // Update the data grid.  
    ordViewSource.View.Refresh();
}

private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    // Get the Order in the row in which the Delete button was clicked.  
    Order obj = e.Parameter as Order;
    Delete_Order(obj);
}

Eseguire l'applicazione

Per avviare il debug, premere F5. Dovrebbero essere visualizzati i dati dei clienti e degli ordini nella griglia e i pulsanti di spostamento dovrebbero funzionare come previsto. Fare clic su Commit per aggiungere un nuovo cliente o ordine al modello dopo aver immesso i dati. Fare clic su Annulla per eseguire il backup di un nuovo cliente o di un nuovo modulo di ordine senza salvare i dati. È possibile apportare modifiche ai clienti e agli ordini esistenti direttamente nelle caselle di testo e tali modifiche vengono scritte automaticamente nel modello.