Erste Schritte mit WPFGetting Started with WPF

In dieser schrittweisen exemplarischen Vorgehensweise wird gezeigt, wie POCO-Typen an WPF-Steuerelemente in einem „main-detail“-Formular gebunden werden.This step-by-step walkthrough shows how to bind POCO types to WPF controls in a "main-detail" form. Die Anwendung verwendet die Entity Framework-APIs zum Auffüllen von Objekten mit Daten aus der Datenbank, zum Nachverfolgen von Änderungen und zum persistenten Speichern von Daten in der Datenbank.The application uses the Entity Framework APIs to populate objects with data from the database, track changes, and persist data to the database.

Das Modell definiert zwei Typen, die eine 1:n-Beziehung eingehen: Category (principal\main) und Product (dependent\detail).The model defines two types that participate in one-to-many relationship: Category (principal\main) and Product (dependent\detail). Das WPF-Datenbindungsframework ermöglicht die Navigation zwischen verbundenen Objekten: durch Auswählen von Zeilen in der Masteransicht wird die Detailansicht mit den entsprechenden untergeordneten Daten aktualisiert.The WPF data-binding framework enables navigation between related objects: selecting rows in the master view causes the detail view to update with the corresponding child data.

Die Screenshots und Codeauflistungen in dieser exemplarischen Vorgehensweise stammen aus Visual Studio 2019 16.6.5.The screen shots and code listings in this walkthrough are taken from Visual Studio 2019 16.6.5.

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.You can view this article's sample on GitHub.

VoraussetzungenPre-Requisites

Um diese exemplarische Vorgehensweise nachvollziehen zu können, müssen Sie Visual Studio 2019 16.3 oder höher mit ausgewählter .NET-Desktopworkload installiert haben.You need to have Visual Studio 2019 16.3 or later installed with the .NET desktop workload selected to complete this walkthrough. Weitere Informationen zur Installation der neuesten Version von Visual Studio finden Sie unter Installieren von Visual Studio.For more information about installing the latest version of Visual Studio, see Install Visual Studio.

Erstellen der AnwendungCreate the Application

  1. Öffnen Sie Visual Studio.Open Visual Studio
  2. Wählen Sie im Startfenster Neues Projekt erstellen aus.On the start window, choose Create new project.
  3. Suchen Sie nach „WPF“, wählen Sie WPF-App (.NET Core) aus, und wählen Sie dann Weiter aus.Search for "WPF," choose WPF App (.NET Core) and then choose Next.
  4. Geben Sie dem Projekt auf dem nächsten Bildschirm einen Namen (z. B. GetStartedWPF ), und wählen Sie Erstellen aus.At the next screen, give the project a name, for example, GetStartedWPF , and choose Create.

Installieren des Entity Framework-NuGet-PaketsInstall the Entity Framework NuGet packages

  1. Klicken Sie mit der rechten Maustaste auf die Projektmappe, und wählen Sie NuGet-Pakete für Projektmappe verwalten aus.Right-click on the solution and choose Manage NuGet Packages for Solution...

    NuGet-Pakete verwalten

  2. Geben Sie im Suchfeld als Suchbegriff entityframeworkcore.sqlite ein.Type entityframeworkcore.sqlite in the search box.

  3. Wählen Sie das Paket Microsoft.EntityFrameworkCore.Sqlite aus.Select the Microsoft.EntityFrameworkCore.Sqlite package.

  4. Wählen Sie das Projekt im rechten Bereich aus, und klicken Sie auf Installieren.Check the project in the right pane and click Install

    SQLite-Paket

  5. Wiederholen Sie die Schritte, um nach entityframeworkcore.proxies zu suchen und Microsoft.EntityFrameworkCore.Proxies zu installieren.Repeat the steps to search for entityframeworkcore.proxies and install Microsoft.EntityFrameworkCore.Proxies.

Hinweis

Als Sie das SQLite-Paket installiert haben, wurde das zugehörige Microsoft.EntityFrameworkCore -Basispaket automatisch heruntergeladen.When you installed the Sqlite package, it automatically pulled down the related Microsoft.EntityFrameworkCore base package. Das Microsoft. EntityFrameworkCore.Proxies -Paket unterstützt „Lazy Loading“ von Daten.The Microsoft.EntityFrameworkCore.Proxies package provides support for "lazy-loading" data. Dies bedeutet, dass bei Entitäten mit untergeordneten Entitäten nur die übergeordneten Elemente beim anfänglichen Laden abgerufen werden.This means when you have entities with child entities, only the parents are fetched on the initial load. Die Proxys erkennen, wenn versucht wird, auf die untergeordneten Entitäten zuzugreifen. Diese werden bei Bedarf automatisch geladen.The proxies detect when an attempt to access the child entities is made and automatically loads them on demand.

Definieren eines ModellsDefine a Model

In dieser exemplarischen Vorgehensweise implementieren Sie ein Modell mit „Code First“.In this walkthrough you will implement a model using "code first." Dies bedeutet, dass EF Core die Datenbanktabellen und das Schema basierend auf den von Ihnen definierten C#-Klassen erstellt.This means that EF Core will create the database tables and schema based on the C# classes you define.

Fügen Sie eine neue Klasse hinzu.Add a new class. Geben Sie ihr den Namen Product.cs, und füllen Sie sie wie folgt mit Daten auf:Give it the name: Product.cs and populate it like this:

Product.cs

namespace GetStartedWPF
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

Fügen Sie dann eine Klasse mit dem Namen Category.cs hinzu, und füllen Sie sie mit folgendem Code auf:Next, add a class named Category.cs and populate it with the following code:

Category.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace GetStartedWPF
{
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
}

Die Products -Eigenschaft für die Category -Klasse und die Category -Eigenschaft für die Product -Klasse sind Navigationseigenschaften.The Products property on the Category class and Category property on the Product class are navigation properties. Im Entity Framework bieten Navigationseigenschaften eine Möglichkeit, in einer Beziehung zwischen zwei Entitätstypen zu navigieren.In Entity Framework, navigation properties provide a way to navigate a relationship between two entity types.

Zusätzlich zur Definition von Entitäten müssen Sie eine Klasse definieren, die von DbContext abgeleitet ist und DbSet<TEntity>-Eigenschaften bereitstellt.In addition to defining entities, you need to define a class that derives from DbContext and exposes DbSet<TEntity> properties. Die DbSet<TEntity>-Eigenschaften informieren den Kontext darüber, welche Typen Sie in das Modell einbeziehen möchten.The DbSet<TEntity> properties let the context know which types you want to include in the model.

Eine Instanz des von DbContext abgeleiteten Typs verwaltet die Entitätsobjekte während der Laufzeit, was das Auffüllen der Objekte mit Daten aus einer Datenbank, die Änderungsnachverfolgung und das persistente Speichern von Daten in der Datenbank umfasst.An instance of the DbContext derived type manages the entity objects during run time, which includes populating objects with data from a database, change tracking, and persisting data to the database.

Fügen Sie dem Projekt eine neue ProductContext.cs-Klasse mit der folgenden Definition hinzu:Add a new ProductContext.cs class to the project with the following definition:

ProductContext.cs

using Microsoft.EntityFrameworkCore;

namespace GetStartedWPF
{
    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }

        protected override void OnConfiguring(
            DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(
                "Data Source=products.db");
            optionsBuilder.UseLazyLoadingProxies();
            base.OnConfiguring(optionsBuilder);
        }
    }
}
  • DbSet informiert EF Core, welche C#-Entitäten der Datenbank zugeordnet werden sollen.The DbSet informs EF Core what C# entities should be mapped to the database.
  • Es gibt eine Vielzahl von Möglichkeiten, DbContext von EF Core zu konfigurieren.There are a variety of ways to configure the EF Core DbContext. Informationen zu diesem Thema finden Sie hier: Konfigurieren eines DbContext.You can read about them in: Configuring a DbContext.
  • In diesem Beispiel wird die OnConfiguring-Überschreibung verwendet, um eine SQLite-Datendatei anzugeben.This example uses the OnConfiguring override to specify a Sqlite data file.
  • Der UseLazyLoadingProxies-Aufruf informiert EF Core, Lazy Loading zu implementieren, damit untergeordnete Entitäten automatisch geladen werden, wenn der Zugriff über das übergeordnete Element erfolgt.The UseLazyLoadingProxies call tells EF Core to implement lazy-loading, so child entities are automatically loaded when accessed from the parent.

Drücken Sie STRG+UMSCHALT+B , oder navigieren Sie zu Build > Projektmappe erstellen , um das Projekt zu kompilieren.Press CTRL+SHIFT+B or navigate to Build > Build Solution to compile the project.

Tipp

Informieren Sie sich über die verschiedenen Möglichkeiten, die Datenbank und EF Core-Modelle synchron zu halten: Verwalten von Datenbankschemas.Learn about the different was to keep your database and EF Core models in sync: Managing Database Schemas.

Lazy LoadingLazy Loading

Die Products -Eigenschaft für die Category -Klasse und die Category -Eigenschaft für die Product -Klasse sind Navigationseigenschaften.The Products property on the Category class and Category property on the Product class are navigation properties. In Entity Framework Core bieten Navigationseigenschaften eine Möglichkeit, in einer Beziehung zwischen zwei Entitätstypen zu navigieren.In Entity Framework Core, navigation properties provide a way to navigate a relationship between two entity types.

EF Core bietet Ihnen die Möglichkeit, verwandte Entitäten aus der Datenbank automatisch zu laden, wenn Sie zum ersten Mal auf die Navigationseigenschaft zugreifen.EF Core gives you an option of loading related entities from the database automatically the first time you access the navigation property. Beachten Sie bei dieser Art von Laden (das als „Lazy Loading“ bezeichnet wird), dass beim ersten Zugriff auf jede Navigationseigenschaft eine separate Abfrage für die Datenbank ausgeführt wird, wenn sich die Inhalte nicht bereits im Kontext befinden.With this type of loading (called lazy loading), be aware that the first time you access each navigation property a separate query will be executed against the database if the contents are not already in the context.

Bei der Verwendung von POCO-Entitätstypen („Plain Old C# Object“) erreicht EF Core Lazy Loading, indem während der Laufzeit Instanzen abgeleiteter Proxytypen erstellt und dann virtuelle Eigenschaften in Ihren Klassen überschrieben werden, um den Ladehook hinzuzufügen.When using "Plain Old C# Object" (POCO) entity types, EF Core achieves lazy loading by creating instances of derived proxy types during runtime and then overriding virtual properties in your classes to add the loading hook. Um Lazy Loading von verwandten Objekten zu erzielen, müssen Sie Navigationseigenschaftsgetter als public und virtual deklarieren ( Overridable in Visual Basic), und Ihre Klasse darf nicht sealed ( NotOverridable in Visual Basic) sein.To get lazy loading of related objects, you must declare navigation property getters as public and virtual ( Overridable in Visual Basic), and your class must not be sealed ( NotOverridable in Visual Basic). Wenn Database First verwendet wird, werden Navigationseigenschaften automatisch als virtuell festgelegt, um Lazy Loading zu ermöglichen.When using Database First, navigation properties are automatically made virtual to enable lazy loading.

Binden von Objekten an SteuerelementeBind Object to Controls

Fügen Sie die Klassen, die im Modell definiert sind, als Datenquellen für diese WPF-Anwendung hinzu.Add the classes that are defined in the model as data sources for this WPF application.

  1. Doppelklicken Sie im Projektmappen-Explorer auf MainWindow.xaml , um das Hauptformular zu öffnen.Double-click MainWindow.xaml in Solution Explorer to open the main form

  2. Wählen Sie die Registerkarte XAML aus, um den XAML-Code zu bearbeiten.Choose the XAML tab to edit the XAML.

  3. Fügen Sie direkt nach dem öffnenden Window-Tag die folgenden Quellen hinzu, um eine Verbindung mit den EF Core-Entitäten herzustellen.Immediately after the opening Window tag, add the following sources to connect to the EF Core entities.

    <Window x:Class="GetStartedWPF.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:GetStartedWPF"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="categoryViewSource"/>
            <CollectionViewSource x:Key="categoryProductsViewSource" 
                                  Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
        </Window.Resources>
    
  4. Dies legt die Quelle für die „übergeordneten“ Kategorien und die zweite Quelle für die „detail“-Produkte fest.This sets up source for the "parent" categories, and second source for the "detail" products.

  5. Fügen Sie dem XAML dann das folgende Markup nach dem schließenden Window.Resources-Tag hinzu.Next, add the following markup to your XAML after the closing Window.Resources tag.

    <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" 
              EnableRowVirtualization="True" 
              ItemsSource="{Binding Source={StaticResource categoryViewSource}}" 
              Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding CategoryId}"
                                Header="Category Id" Width="SizeToHeader"
                                IsReadOnly="True"/>
            <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                                Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
    
  6. Beachten Sie, dass CategoryId auf ReadOnly festgelegt ist, da die ID von der Datenbank zugewiesen wird und nicht geändert werden kann.Note that the CategoryId is set to ReadOnly because it is assigned by the database and cannot be changed.

Hinzufügen eines DetailrastersAdding a Details Grid

Da das Raster nun zum Anzeigen von Kategorien vorhanden ist, kann das Detailraster zum Anzeigen von Produkten hinzugefügt werden.Now that the grid exists to display categories, the details grid can be added to show products.

MainWindow.xaml

<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" 
          EnableRowVirtualization="True" 
          ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}" 
          Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected" 
          RenderTransformOrigin="0.488,0.251">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

Fügen Sie schließlich eine Schaltfläche Save hinzu, und verbinden Sie das click-Ereignis mit Button_Click.Finally, add a Save button and wire in the click event to Button_Click.

<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" 
        Click="Button_Click" Height="20" Width="123"/>

Die Entwurfsansicht sollte wie folgt aussehen:Your design view should look like this:

Screenshot des WPF-Designers

Hinzufügen von Code, der die Dateninteraktion verarbeitetAdd Code that Handles Data Interaction

Es ist an der Zeit, dem Hauptfenster einige Ereignishandler hinzuzufügen.It's time to add some event handlers to the main window.

  1. Klicken Sie im XAML-Fenster auf das Fenster <Window> -Element, um das Hauptfenster auszuwählen.In the XAML window, click on the <Window> element, to select the main window.

  2. Wählen Sie im Fenster Eigenschaften oben rechts Ereignisse aus, und doppelklicken Sie dann auf das Textfeld rechts neben der Bezeichnung Geladen.In the Properties window choose Events at the top right, then double-click the text box to right of the Loaded label.

    Eigenschaften des Hauptfensters

Damit gelangen Sie zum Code Behind für das Formular. Nun bearbeiten wir den Code so, dass ProductContext für den Datenzugriff verwendet wird.This brings you to the code behind for the form, we'll now edit the code to use the ProductContext to perform data access. Aktualisieren Sie den Code wie unten gezeigt.Update the code as shown below.

Der Code deklariert eine Instanz von ProductContext mit langer Ausführungszeit.The code declares a long-running instance of ProductContext. Das ProductContext-Objekt wird verwendet, um Daten abzufragen und in der Datenbank zu speichern.The ProductContext object is used to query and save data to the database. Die Dispose()-Methode für die ProductContext-Instanz wird dann von der überschriebenen OnClosing-Methode aufgerufen.The Dispose() method on the ProductContext instance is then called from the overridden OnClosing method. Die Codekommentare erläutern die Funktionsweise der einzelnen Schritte.The code comments explain what each step does.

MainWindow.xaml.cs

using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GetStartedWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ProductContext _context = 
            new ProductContext();

        private CollectionViewSource categoryViewSource;

        public MainWindow()
        {
            InitializeComponent();
            categoryViewSource = 
                (CollectionViewSource)FindResource(nameof(categoryViewSource));            
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // this is for demo purposes only, to make it easier
            // to get up and running
            _context.Database.EnsureCreated();

            // load the entities into EF Core
            _context.Categories.Load();

            // bind to the source
            categoryViewSource.Source =
                _context.Categories.Local.ToObservableCollection();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // all changes are automatically tracked, including
            // deletes!
            _context.SaveChanges();

            // this forces the grid to refresh to latest values
            categoryDataGrid.Items.Refresh();
            productsDataGrid.Items.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            // clean up database connections
            _context.Dispose();
            base.OnClosing(e);
        }
    }
}

Hinweis

Der Code verwendet einen Aufruf von EnsureCreated() zum Erstellen der Datenbank bei der ersten Ausführung.The code uses a call to EnsureCreated() to build the database on the first run. Dies ist für Demos akzeptabel, aber in Produktions-Apps sollten Sie Migrationen verwenden, um Ihr Schema zu verwalten.This is acceptable for demos, but in production apps you should look at migrations to manage your schema. Der Code wird auch synchron ausgeführt, da er eine lokale SQLite-Datenbank verwendet.The code also executes synchronously because it uses a local SQLite database. In Produktionsszenarien, in denen normalerweise ein Remoteserver verwendet wird, sollten Sie die Verwendung der asynchronen Versionen der Methoden Load und SaveChanges in Erwägung ziehen.For production scenarios that typically involve a remote server, consider using the asynchronous versions of the Load and SaveChanges methods.

Testen der WPF-AnwendungTest the WPF Application

Kompilieren Sie die Anwendung, und führen Sie sie aus, indem Sie F5 drücken oder Debuggen > Debuggen starten auswählen.Compile and run the application by pressing F5 or choosing Debug > Start Debugging. Die Datenbank sollte automatisch mit einer Datei namens products.db erstellt werden.The database should be automatically created with a file named products.db. Geben Sie einen Kategorienamen ein, und drücken Sie die EINGABETASTE. Fügen Sie dann dem unteren Raster Produkte hinzu.Enter a category name and hit enter, then add products to the lower grid. Klicken Sie auf „Speichern“, und beobachten Sie, wie das Raster mit den von der Datenbank bereitgestellten IDs aktualisiert wird.Click save and watch the grid refresh with the database provided ids. Markieren Sie eine Zeile, und klicken Sie auf Löschen , um die Zeile zu entfernen.Highlight a row and hit Delete to remove the row. Die Entität wird gelöscht, wenn Sie auf Speichern klicken.The entity will be deleted when you click Save.

Ausgeführte Anwendung

Benachrichtigung der EigenschaftenänderungProperty Change Notification

Dieses Beispiel basiert auf vier Schritten, um die Entitäten mit der Benutzeroberfläche zu synchronisieren.This example relies on four steps to synchronize the entities with the UI.

  1. Der anfängliche Aufruf von _context.Categories.Load() lädt die Kategoriedaten.The initial call _context.Categories.Load() loads the categories data.
  2. Die Lazy Loading-Proxys laden die Daten der abhängigen Produkte.The lazy-loading proxies load the dependent products data.
  3. Die integrierte Änderungsnachverfolgung von EF Core nimmt die erforderlichen Änderungen an Entitäten vor, einschließlich Einfügungen und Löschungen, wenn _context.SaveChanges() aufgerufen wird.EF Core's built-in change tracking makes the necessary modifications to entities, including insertions and deletions, when _context.SaveChanges() is called.
  4. Die Aufrufe von DataGridView.Items.Refresh() erzwingen einen erneuten Ladevorgang mit den neu generierten IDs.The calls to DataGridView.Items.Refresh() force a reload with the newly generated ids.

Dies funktioniert für unser Beispiel mit ersten Schritten, aber Sie benötigen möglicherweise zusätzlichen Code für andere Szenarien.This works for our getting started sample, but you may require additional code for other scenarios. WPF-Steuerelemente rendern die Benutzeroberfläche, indem sie die Felder und Eigenschaften für Ihre Entitäten lesen.WPF controls render the UI by reading the fields and properties on your entities. Wenn Sie einen Wert in der Benutzeroberfläche (User Interface, UI) bearbeiten, wird dieser Wert an Ihre Entität übergeben.When you edit a value in the user interface (UI), that value is passed to your entity. Wenn Sie den Wert einer Eigenschaft direkt für Ihre Entität ändern, z. B. das Laden aus der Datenbank, spiegelt WPF die Änderungen nicht sofort in der Benutzeroberfläche wider.When you change the value of a property directly on your entity, such as loading it from the database, WPF will not immediately reflect the changes in the UI. Die Rendering-Engine muss über die Änderungen benachrichtigt werden.The rendering engine must be notified of the changes. Das Projekt hat dies durch manuelles Aufrufen von Refresh() erreicht.The project did this by manually calling Refresh(). Eine einfache Möglichkeit, diese Benachrichtigung zu automatisieren, besteht darin, die INotifyPropertyChanged-Schnittstelle zu implementieren.An easy way to automate this notification is by implementing the INotifyPropertyChanged interface. WPF-Komponenten erkennen die Schnittstelle automatisch und registrieren sich für Änderungsereignisse.WPF components will automatically detect the interface and register for change events. Die Entität ist für das Auslösen dieser Ereignisse verantwortlich.The entity is responsible for raising these events.

Tipp

Weitere Informationen zum Verarbeiten von Änderungen finden Sie unter: Implementieren von Benachrichtigungen bei Eigenschaftenänderungen.To learn more about how to handle changes, read: How to implement property change notification.

Nächste SchritteNext Steps

Weitere Informationen zum Konfigurieren eines DbContext.Learn more about Configuring a DbContext.