Erstellen einer einfachen Datenanwendung mit WPF und Entity Framework 6
Warnung
Wenn Sie Visual Studio 2022 verwenden, sollten Sie für dieses Tutorial Visual Studio 2022, Version 17.3 Preview 3 oder höher verwenden.
Diese exemplarische Vorgehensweise zeigt Ihnen, wie Sie eine einfache Forms-over-Data-Anwendung in Visual Studio erstellen. Die App verwendet SQL Server LocalDB, die Northwind-Datenbank, Entity Framework 6 (nicht Entity Framework Core) und Windows Presentation Foundation für .NET Framework (nicht .NET Core oder .NET 5 oder höher). Die App zeigt, wie Sie grundlegende Datenbindungen mit einer Master/Detail-Ansicht durchführen können, und enthält einen benutzerdefinierten Bindungsnavigator mit den Schaltflächen Weiter, Zurück, Anfang, Ende, Aktualisieren und Löschen.
Dieser Artikel konzentriert sich auf die Verwendung von Datentools in Visual Studio und hat nicht den Anspruch, die zugrunde liegenden Technologien im Detail zu erläutern. Es wird vorausgesetzt, dass Sie mit XAML, Entity Framework und SQL vertraut sind. Die MVVM-Architektur (Model-View-ViewModel), die bei WPF-Anwendungen standardmäßig zum Einsatz kommt, wird in diesem Beispiel ebenfalls nicht erläutert. Sie können diesen Code jedoch mit wenigen Änderungen in Ihre eigene MVVM-Anwendung kopieren.
Den endgültigen Code für dieses Tutorial können Sie auf GitHub unter Visual Studio Tutorial Samples – EF6 finden.
Installieren und Herstellen einer Verbindung mit Northwind
In diesem Beispiel werden SQL Server Express LocalDB und die Northwind-Beispieldatenbank verwendet. Wenn der ADO.NET-Datenanbieter für dieses Produkt Entity Framework unterstützt, sollte er auch mit anderen SQL-Datenbankprodukten funktionieren.
Wenn Sie nicht über SQL Server Express LocalDB verfügen, führen Sie die Installation mit dem Visual Studio-Installer durch. Im Visual Studio-Installer können Sie SQL Server Express LocalDB als Teil der Workload Datenspeicherung und -verarbeitung oder als einzelne Komponente installieren.
Installieren Sie die Northwind-Beispieldatenbank, indem Sie die folgenden Schritte ausführen:
Öffnen Sie in Visual Studio das Fenster SQL Server-Objekt-Explorer. (Der SQL Server-Objekt-Explorer wird als Teil der Workload Datenspeicherung und -verarbeitung im Visual Studio-Installer installiert.) Erweitern Sie den Knoten SQL Server. Klicken Sie mit der rechten Maustaste auf Ihre LocalDB-Instanz, und wählen Sie Neue Abfrage aus.
Ein Abfrage-Editor-Fenster wird geöffnet.
Kopieren Sie das Northwind-Transact-SQL-Skript in die Zwischenablage. Dieses T-SQL-Skript erstellt die Northwind-Datenbank von Grund auf neu und füllt sie mit Daten auf.
Fügen Sie das T-SQL-Skript in den Abfrage-Editor ein, und klicken Sie dann auf die Schaltfläche Ausführen.
Nach kurzer Zeit wird die Ausführung der Abfrage abgeschlossen, und die Northwind-Datenbank wird erstellt.
Fügen Sie neue Verbindungen für Northwind hinzu.
Konfigurieren des Projekts
Erstellen Sie in Visual Studio ein neues C#-Projekt vom Typ WPF-App (.NET Framework).
Fügen Sie das NuGet-Paket für Entity Framework 6 hinzu. Wählen Sie im Projektmappen-Explorer den Projektknoten aus. Wählen Sie im Hauptmenü Projekt>NuGet-Pakete verwalten aus.
Klicken Sie im NuGet-Paket-Manager auf den Link Durchsuchen. Entity Framework wird wahrscheinlich als erstes Paket in der Liste angezeigt. Klicken Sie im rechten Bereich auf Installieren, und folgen Sie den Anweisungen. Sie werden im Ausgabefenster informiert, wenn die Installation abgeschlossen ist.
Jetzt können Sie mithilfe von Visual Studio ein Modell auf Grundlage der Northwind-Datenbank erstellen.
Erstellen des Modells
Klicken Sie erst mit der rechten Maustaste auf den Projektknoten im Projektmappen-Explorer, und wählen Sie Hinzufügen>Neues Element aus. Wählen Sie im linken Bereich unterhalb des Knotens „C#“ die Option Daten und im mittleren Bereich die Option ADO.NET Entity Data Model aus.
Geben Sie dem Modell den Namen
Northwind_model
, und wählen Sie Hinzufügen aus. Der Assistent für Entity Data Model wird geöffnet. Wählen Sie EF-Designer aus Datenbank aus, und wählen Sie anschließend Weiter aus.Wählen Sie auf dem nächsten Bildschirm Ihre LocalDB Northwind-Verbindung (zum Beispiel „(localdb)\MSSQLLocalDB“) aus, geben Sie die Northwind-Datenbank an, und klicken Sie auf Weiter.
Wenn keine Verbindung angezeigt wird, wählen Sie Neue Verbindung und im Dialogfeld Datenquelle wählen die Optionen Microsoft SQL Server und Weiter aus. Geben Sie im Dialogfeld Verbindungseigenschaften
(localdb)\MSSQLLocalDB
ein, und wählen Sie unter Datenbankname auswählen oder eingeben die Option Northwind aus. Klicken Sie dann auf OK.Wählen Sie bei entsprechender Aufforderung die verwendete Version von Entity Framework aus.
Wählen Sie auf der nächsten Seite des Assistenten aus, welche Tabellen, gespeicherten Prozeduren und sonstigen Datenbankobjekte in das Entity Framework-Modell aufgenommen werden sollen. Erweitern Sie in der Strukturansicht den Knoten „dbo“, und klicken Sie auf Customers, Orders und Order Details. Lassen Sie die Standardeinstellungen aktiviert, und klicken Sie auf Fertig stellen.
Der Assistent generiert die C#-Klassen, die das Entity Framework-Modell repräsentieren. Bei den Klassen handelt es sich um einfache C#-Klassen, die wir mit der WPF-Benutzeroberfläche verknüpfen. Die
.edmx
-Datei beschreibt die Beziehungen und weitere Metadaten, mit denen die Klassen den Objekten in der Datenbank zugeordnet werden. Die.tt
-Dateien sind T4-Vorlagen, die den Code zur Arbeit mit dem Modell und zum Speichern von Änderungen in der Datenbank generieren. Alle diese Dateien werden im Projektmappen-Explorer unter dem Knoten „Northwind_model“ angezeigt:Mit der Designeroberfläche für die
.edmx
-Datei können Sie einige Eigenschaften und Beziehungen im Modell ändern. In dieser exemplarischen Vorgehensweise wird der Designer nicht verwendet.Die
.tt
-Dateien sind universell einsetzbar, und Sie müssen eine von ihnen so anpassen, dass sie mit der für „ObservableCollections“ benötigten WPF-Datenbindung funktioniert. Erweitern Sie im Projektmappen-Explorer den Knoten „Northwind_model“, bis Northwind_model.tt anzeigt wird. (Stellen Sie sicher, dass Sie sich nicht in der .Context.tt-Datei befinden, die sich direkt unter der.edmx
-Datei befindet.)Ersetzen Sie die zwei Vorkommen von ICollection durch ObservableCollection<T>.
Ersetzen Sie etwa in Zeile 51 das erste Vorkommen von HashSet<T> durch ObservableCollection<T>. Ersetzen Sie nicht das zweite Vorkommen von „HashSet“.
Ersetzen Sie das einzige Vorkommen von System.Collections.Generic (etwa in Zeile 431) durch System.Collections.ObjectModel.
Drücken Sie F5 oder STRG+F5, um das Projekt zu erstellen und auszuführen. Bei der ersten Ausführung der Anwendung sind die Modellklassen für den Datenquellen-Assistenten sichtbar.
Jetzt können Sie dieses Modell mit der XAML-Seite verknüpfen, damit Sie die Daten anzeigen, ändern und darin navigieren können.
Datenbindung zwischen Modell und XAML-Seite
Sie können zwar eigenen Code für die Datenbindung schreiben, aber es ist wesentlich einfacher, Visual Studio diese Aufgabe zu überlassen.
Wählen Sie im Hauptmenü die Optionen Projekt>Neue Datenquelle hinzufügen, um den Assistenten zum Konfigurieren von Datenquellen zu öffnen. Wählen Sie Objekt aus, denn Sie erstellen eine Bindung an die Modellklassen, nicht an die Datenbank:
Erweitern Sie den Knoten für Ihr Projekt, und wählen Sie Customer aus. (Quellen für „Orders“ werden automatisch aus der Navigationseigenschaft „Orders“ in „Customer“ generiert).
Klicken Sie auf Fertig stellen.
Navigieren Sie in der Codeansicht zu MainWindow.xaml. Für die Zwecke dieses Beispiels halten wir die XAML einfach. Ändern Sie den Titel von „MainWindow“ in einen aussagekräftigeren Namen, und legen Sie seine Höhe und Breite zunächst auf 600 × 800 fest. Sie können die Abmessungen später jederzeit ändern. Fügen Sie dem Hauptraster nun diese drei Zeilendefinitionen hinzu: eine Zeile für die Navigationsschaltflächen, eine für die Kundendaten und eine für das Raster, das die Bestellungen anzeigt:
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
Öffnen Sie jetzt die Datei MainWindow.xaml, um sie im Designer anzuzeigen. Dies bewirkt, dass das Fenster Datenquellen als Option am Rand des Visual Studio-Fensters neben der Toolbox angezeigt wird. Klicken Sie auf die Registerkarte, um das Fenster zu öffnen. Alternativ können Sie UMSCHALT+ALT+D drücken oder Ansicht>Weitere Fenster>Datenquellen auswählen. Wir werden jede Eigenschaft der Klasse „Customers“ in einem eigenen Textfeld anzeigen. Klicken Sie zunächst auf den Pfeil im Kombinationsfeld Customers, und wählen Sie Details aus. Ziehen Sie den Knoten dann auf den mittleren Teil der Entwurfsfläche, um dem Designer mitzuteilen, dass Sie den Knoten in der mittleren Reihe platzieren möchten. Wenn Sie die falsche Position gewählt haben, können Sie die Zeile später manuell in der XAML (
Grid.Row="1"
) angeben. Standardmäßig werden die Steuerelemente vertikal in einem Rasterelement platziert, aber zu diesem Zeitpunkt können Sie sie nach Belieben im Formular anordnen. Es könnte zum Beispiel sinnvoll sein, das Textfeld Name ganz oben zu platzieren, oberhalb der Adresse. Die Beispielanwendung für den vorliegenden Artikel sortiert die Felder neu und ordnet sie in zwei Spalten an.In der XAML-Ansicht wird nun ein neues
Grid
-Element in Zeile 1 (der mittleren Zeile) des übergeordneten Rasters angezeigt. Das übergeordnete Raster umfasst einDataContext
-Attribut. Dieses verweist auf ein CollectionViewSource, das demWindows.Resources
-Element hinzugefügt wurde. In diesem Datenkontext wird bei der Bindung des ersten Textfelds an Address dieser Name derAddress
-Eigenschaft im aktuellenCustomer
-Objekt inCollectionViewSource
zugeordnet.<Grid DataContext="{StaticResource customerViewSource}">
Wenn ein Kunde in der oberen Fensterhälfte sichtbar ist, möchten Sie die zugehörigen Bestellungen in der unteren Fensterhälfte anzeigen. Sie zeigen die Bestellungen in einem einzelnen Rasteransichtssteuerelement an. Damit die Master/Detail-Datenbindung wie erwartet funktioniert, ist es wichtig, dass Sie die Bindung an die Eigenschaft „Orders“ in der Klasse „Customers“ und nicht an den separaten Knoten „Orders“ vornehmen. Ziehen Sie die Eigenschaft „Orders“ der Klasse „Customers“ in die untere Formularhälfte, damit der Designer sie in Zeile 2 platziert:
Visual Studio hat den gesamten Bindungscode generiert, der die UI-Steuerelemente mit den Ereignissen im Modell verbindet. Sie müssen zur Anzeige von Daten lediglich etwas Code schreiben, um das Model aufzufüllen. Navigieren Sie zunächst zu MainWindow.xaml.cs, und fügen Sie der Klasse „MainWindow“ für den Datenkontext einen Datenmember hinzu. Dieses für Sie generierte Objekt fungiert als eine Art Steuerelement, das Änderungen und Ereignisse im Modell nachverfolgt. Außerdem fügen Sie CollectionViewSource-Datenelemente für Kunden und Bestellungen sowie die zugehörige Konstruktorinitialisierungslogik zum vorhandenen Konstruktor
MainWindow()
hinzu. Der obere Abschnitt der Klasse sollte wie folgt aussehen: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; }
Falls noch nicht vorhanden, fügen Sie eine
using
-Anweisung für „System.Data.Entity“ hinzu, um die ErweiterungsmethodeLoad
in den Geltungsbereich einzubeziehen:using System.Data.Entity;
Scrollen Sie nun nach unten, und suchen Sie den
Window_Loaded
-Ereignishandler. Beachten Sie, dass Visual Studio ein CollectionViewSource-Objekt hinzugefügt hat. Es repräsentiert das NorthwindEntities-Objekt, das Sie beim Erstellen des Modells ausgewählt haben. Sie haben das Objekt schon hinzugefügt, deshalb benötigen Sie es hier nicht. Ersetzen wir den Code inWindow_Loaded
, damit die Methode nun wie folgt aussieht: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; }
Drücken Sie F5. Es sollten die Details zum ersten Kunden angezeigt werden, der in der „CollectionViewSource“ abgerufen wurde. Außerdem sollten Ihre Bestellungen im Datenraster angezeigt werden. Die Formatierung ist nicht optimal, also bringen wir das in Ordnung. Sie können auch eine Möglichkeit zum Anzeigen der weiteren Datensätze und zum Ausführen grundlegender Erstellungs-, Lese-, Aktualisierungs- und Löschvorgänge (CRUD) schaffen.
Anpassen des Seitendesigns und Hinzufügen von Rastern für neue Kunden und Bestellungen
Die von Visual Studio erzeugte Standardanordnung ist für Ihre Anwendung nicht ideal, deshalb stellen wir hier die endgültige XAML bereit, die Sie in Ihren Code kopieren können. Sie benötigen außerdem einige „Formulare“ (bei denen es sich eigentlich um Raster handelt), damit der Benutzer einen neuen Kunden oder eine neue Bestellung hinzufügen kann. Um neue Kunden und Bestellungen hinzufügen zu können, benötigen Sie einen separaten Satz von Textfeldern, die nicht an die CollectionViewSource
-Daten gebunden sind. Sie steuern, welches Raster der Benutzer zu einem bestimmten Zeitpunkt sieht, indem Sie die Visible-Eigenschaft in den Handlermethoden einstellen. Schließlich fügen Sie jeder Zeile in der Tabelle „Orders“ eine Schaltfläche zum Löschen hinzu, damit der Benutzer eine einzelne Bestellung löschen kann.
Fügen Sie dem Element Windows.Resources
in MainWindow.xaml zunächst diese Stile hinzu:
<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>
Ersetzen Sie dann das gesamte äußere Raster durch dieses 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>
Hinzufügen von Schaltflächen zum Navigieren, Hinzufügen, Aktualisieren und Löschen
In Windows Forms-Anwendungen erhalten Sie ein BindingNavigator-Objekt mit Schaltflächen, über die Sie durch die Zeilen einer Datenbank navigieren und grundlegende CRUD-Vorgänge ausführen können. WPF stellt kein solches BindingNavigator-Objekt zur Verfügung, aber Sie können es ganz einfach erstellen. Dazu verwenden Sie Schaltflächen in einem horizontalen „StackPanel“ und verknüpfen die Schaltflächen mit Befehlen, die an Methoden im zugrunde liegenden Code gebunden sind.
Die Befehlslogik besteht aus vier Teilen: (1) den Befehlen, (2) den Bindungen, (3) den Schaltflächen und (4) den Befehlshandlern im CodeBehind.
Hinzufügen von Befehlen, Bindungen und Schaltflächen in XAML
Fügen Sie der Datei MainWindow.xaml zunächst die Befehle innerhalb des
Windows.Resources
-Elements hinzu:<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"/>
Ein CommandBinding-Element ordnet ein
RoutedUICommand
-Ereignis einer Methode im CodeBehind zu. Fügen Sie diesesCommandBindings
-Element nach dem schließendenWindows.Resources
-Tag ein:<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>
Fügen Sie jetzt das
StackPanel
mit den Schaltflächen zum Navigieren, Hinzufügen, Löschen und Aktualisieren hinzu. Fügen SieWindows.Resources
zunächst diesen Stil hinzu:<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>
Anschließend fügen Sie diesen Code direkt hinter
RowDefinitions
für das äußereGrid
-Element ein, und zwar oben auf der XAML-Seite:<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>
Hinzufügen von Befehlshandlern zur MainWindow-Klasse
Das CodeBehind ist bis auf die Add- und Delete-Methoden minimal gehalten. Die Navigation erfolgt durch den Aufruf von Methoden für die View-Eigenschaft von „CollectionViewSource“. Der DeleteOrderCommandHandler
zeigt, wie Sie ein kaskadierendes Delete für eine Bestellung durchführen. Zuerst müssen wir die zugehörigen „Order_Details“ löschen. Der UpdateCommandHandler
fügt der Sammlung einen neuen Kunden oder eine neue Bestellung hinzu oder aktualisiert bereits vorhandene Kunden oder Bestellungen anhand der Änderungen, die der Benutzer in den Textfeldern vorgenommen hat.
Fügen Sie diese Handlermethoden der Klasse „MainWindow“ in MainWindow.xaml.cs hinzu. Wenn „CollectionViewSource“ für die Tabelle „Customers“ einen anderen Namen aufweist, müssen Sie den Namen in jeder dieser Methoden anpassen:
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);
}
Ausführen der Anwendung
Drücken Sie F5, um mit dem Debuggen zu beginnen. Das Raster sollte mit Kunden- und Bestelldaten aufgefüllt werden, und die Navigationsschaltflächen sollten wie erwartet funktionieren. Klicken Sie auf Commit, um dem Modell einen neuen Kunden oder eine neue Bestellung hinzuzufügen, nachdem Sie die Daten eingegeben haben. Klicken Sie auf Abbrechen, um ein Formular für einen neuen Kunden oder eine neue Bestellung zu verlassen, ohne die Daten zu speichern. Sie können Änderungen an vorhandenen Kunden und Bestellungen direkt in den Textfeldern vornehmen, und diese Änderungen werden automatisch in das Modell geschrieben.