Freigeben über


Erste Schritte mit WPF

In dieser Schritt-für-Schritt-Anleitung wird gezeigt, wie POCO-Typen in einem "Master-Detail"-Formular an WPF-Steuerelemente gebunden werden. Die Anwendung verwendet die Entity Framework-APIs, um Objekte mit Daten aus der Datenbank aufzufüllen, Änderungen nachzuverfolgen und Daten in der Datenbank beizubehalten.

Das Modell definiert zwei Typen, die an einer 1:n-Beziehung teilnehmen: Kategorie (Haupt) und Produkt (abhängig/Detail). Das WPF-Datenbindungsframework ermöglicht die Navigation zwischen verwandten Objekten: Das Auswählen von Zeilen in der Masteransicht bewirkt, dass die Detailansicht mit den entsprechenden untergeordneten Daten aktualisiert wird.

Die Screenshots und Codeauflistungen in dieser exemplarischen Vorgehensweise stammen aus Visual Studio 2019 16.6.5.

Tipp

Sie können das Beispiel dieses Artikels auf GitHubanzeigen.

Voraussetzungen

Sie müssen Visual Studio 2019 16.3 oder höher mit ausgewählter .NET-Desktoparbeitsauslastung installiert haben, um diese exemplarische Vorgehensweise abzuschließen. Weitere Informationen zum Installieren der neuesten Version von Visual Studio finden Sie unter Installieren von Visual Studio.

Erstellen der Anwendung

  1. Öffnen Sie Visual Studio.
  2. Wählen Sie im Startfenster " Neues Projekt erstellen" aus.
  3. Suchen Sie nach "WPF", wählen Sie WPF-App (.NET Core) und dann "Weiter" aus.
  4. Geben Sie auf dem nächsten Bildschirm dem Projekt einen Namen, z. B. "GetStartedWPF", und wählen Sie "Erstellen" aus.

Installieren der Entity Framework-NuGet-Pakete

  1. Klicken Sie mit der rechten Maustaste auf die Lösung, und wählen Sie "NuGet-Pakete für Lösung verwalten" aus...

    Verwalten von NuGet-Paketen

  2. Geben Sie im Suchfeld als Suchbegriff entityframeworkcore.sqlite ein.

  3. Wählen Sie das Microsoft.EntityFrameworkCore.Sqlite-Paket aus.

  4. Überprüfen Sie das Projekt im rechten Bereich, und klicken Sie auf "Installieren".

    Sqlite-Paket

  5. Wiederholen Sie die Schritte, um entityframeworkcore.proxies zu suchen und Microsoft.EntityFrameworkCore.Proxies zu installieren.

Hinweis

Wenn Sie das Sqlite-Paket installiert haben, hat es automatisch das zugehörige Microsoft.EntityFrameworkCore-Basispaket abgerufen. Das Microsoft.EntityFrameworkCore.Proxies-Paket bietet Unterstützung für "lazy-loading"-Daten. Dies bedeutet, dass, wenn Sie Entitäten mit untergeordneten Entitäten haben, nur die übergeordneten Entitäten während des ersten Ladevorgangs abgerufen werden. Die Proxys erkennen, wann versucht wird, auf die untergeordneten Entitäten zuzugreifen, und laden sie bei Bedarf automatisch.

Definieren eines Modells

In dieser exemplarischen Vorgehensweise implementieren Sie ein Modell mit "Code zuerst". Dies bedeutet, dass EF Core die Datenbanktabellen und das Schema basierend auf den von Ihnen definierten C#-Klassen erstellt.

Fügen Sie eine neue Klasse hinzu. Geben Sie ihm den Namen: Product.cs und füllen Sie ihn wie folgt auf:

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 als Nächstes eine Benannte Category.cs Klasse hinzu, und füllen Sie sie mit dem folgenden Code auf:

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 der Category-Klasse und die Category-Eigenschaft der Product-Klasse sind Navigationseigenschaften. In Entity Framework bieten Navigationseigenschaften eine Möglichkeit zum Navigieren in einer Beziehung zwischen zwei Entitätstypen.

Zusätzlich zum Definieren von Entitäten müssen Sie eine Klasse definieren, die von DbContext abgeleitet wird und DbSet<TEntity-Eigenschaften> verfügbar macht. Die DbSet<TEntity-Eigenschaften> lassen den Kontext wissen, welche Typen in das Modell eingeschlossen werden sollen.

Eine Instanz des vom DbContext abgeleiteten Typs verwaltet die Entitätsobjekte während der Laufzeit, einschließlich auffüllen von Objekten mit Daten aus einer Datenbank, Änderungsnachverfolgung und Beibehalten von Daten in der Datenbank.

Fügen Sie dem Projekt eine neue ProductContext.cs Klasse mit der folgenden Definition hinzu:

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();
        }
    }
}
  • DbSet informiert EF Core darüber, welche C#-Entitäten der Datenbank zugeordnet werden sollen.
  • Es gibt eine Vielzahl von Möglichkeiten zum Konfigurieren des EF Core DbContext. Informationen dazu finden Sie unter: Konfigurieren eines DbContexts.
  • In diesem Beispiel wird die OnConfiguring Überschreibung verwendet, um eine Sqlite-Datendatei anzugeben.
  • Der UseLazyLoadingProxies Aufruf weist EF Core an, Lazy-Loading zu implementieren, sodass untergeordnete Objekte beim Zugriff von übergeordneten Elementen automatisch geladen werden.

Drücken Sie STRG+UMSCHALT+B oder navigieren Sie zu Erstellen > Lösung erstellen, um das Projekt zu kompilieren.

Tipp

Erfahren Sie mehr über die unterschiedlichen Möglichkeiten, um Ihre Datenbank und EF Core-Modelle synchron zu halten: Verwalten von Datenbankschemas.

Faules Laden

Die Products-Eigenschaft der Category-Klasse und die Category-Eigenschaft der Product-Klasse sind Navigationseigenschaften. In Entity Framework Core bieten Navigationseigenschaften eine Möglichkeit zum Navigieren in einer Beziehung zwischen zwei Entitätstypen.

EF Core bietet Ihnen die Möglichkeit, verwandte Entitäten automatisch aus der Datenbank zu laden, wenn Sie zum ersten Mal auf die Navigationseigenschaft zugreifen. Beachten Sie bei diesem Ladetyp (als faules Laden bezeichnet), dass beim ersten Zugriff auf jede Navigationseigenschaft eine separate Abfrage für die Datenbank ausgeführt wird, wenn sich der Inhalt nicht bereits im Kontext befindet.

Bei Verwendung von "Plain Old C# Object" (POCO)-Entitätstypen erzielt EF Core Lazy Loading, indem während der Laufzeit Instanzen abgeleiteter Proxytypen erstellt und dann virtuelle Eigenschaften in Ihren Klassen überschrieben werden, um das Laden zu ermöglichen. Um Lazy-Loading von verwandten Objekten zu erreichen, müssen Sie Navigationseigenschafts-Getter als öffentlich und virtuell (in Visual Basic überschreibbar) deklarieren, und Ihre Klasse darf nicht versiegelt sein (NotOverridable in Visual Basic). Bei Verwendung von Database First werden Navigationseigenschaften automatisch virtual gemacht, um Lazy Loading zu ermöglichen.

Objekt an Steuerelemente binden

Fügen Sie die Klassen hinzu, die im Modell als Datenquellen für diese WPF-Anwendung definiert sind.

  1. Doppelklicken Sie im Projektmappen-Explorer auf "MainWindow.xaml" , um das Hauptformular zu öffnen.

  2. Wählen Sie die XAML-Registerkarte aus, um den XAML-Code zu bearbeiten.

  3. Fügen Sie unmittelbar nach dem öffnenden Window Tag die folgenden Quellen hinzu, um eine Verbindung mit den EF Core-Entitäten herzustellen.

    <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. Dadurch wird die Quelle für die "übergeordneten" Kategorien eingerichtet, sowie eine zweite Quelle für die "Detail"-Produkte.

  5. Fügen Sie als Nächstes nach dem öffnenden Grid Tag das folgende Markup zu Ihrem XAML-Code hinzu.

    <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 gesetzt ist, weil es von der Datenbank zugewiesen wird und nicht geändert werden kann.

Hinzufügen eines Detailrasters

Nachdem das Raster zum Anzeigen von Kategorien vorhanden ist, kann das Detailraster hinzugefügt werden, um Produkte anzuzeigen. Fügen Sie dies innerhalb des Grid Elements nach dem Categories-Element DataGrid hinzu.

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 Save Schaltfläche hinzu, und verknüpfen Sie das Click-Ereignis mit Button_Click.

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

Ihre Entwurfsansicht sollte wie folgt aussehen:

Screenshot des WPF-Designers

Hinzufügen von Code zur Behandlung von Dateninteraktionen

Es ist an der Zeit, dem Hauptfenster einige Ereignishandler hinzuzufügen.

  1. Klicken Sie im XAML-Fenster auf das <Window-Element> , um das Hauptfenster auszuwählen.

  2. Wählen Sie im Fenster "Eigenschaften"die Option "Ereignisse " oben rechts aus, und doppelklicken Sie dann auf das Textfeld rechts neben der Beschriftung "Geladen ".

    Hauptfenstereigenschaften

Dadurch gelangen Sie zum Code-Behind des Formulars. Wir werden nun den Code bearbeiten, um den ProductContext zu verwenden und damit Datenzugriff durchzuführen. Aktualisieren Sie den Code wie unten dargestellt.

Der Code deklariert eine lang andauernde Instanz von ProductContext. Das ProductContext Objekt wird verwendet, um Daten in der Datenbank abzufragen und zu speichern. Die Dispose() Methode für die ProductContext Instanz wird dann von der überschriebenen OnClosing Methode aufgerufen. In den Codekommentaren wird erläutert, was jeder Schritt tut.

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 zum EnsureCreated() Erstellen der Datenbank bei der ersten Ausführung. Dies ist für Demos akzeptabel, aber in Produktions-Apps sollten Sie sich Migrationen ansehen, um Ihr Schema zu verwalten. Der Code wird auch synchron ausgeführt, da er eine lokale SQLite-Datenbank verwendet. Für Produktionsszenarien, die in der Regel einen Remoteserver umfassen, sollten Sie die asynchronen Versionen der Load Und SaveChanges Methoden in Betracht ziehen.

Testen der WPF-Anwendung

Kompilieren Sie die Anwendung, und führen Sie sie aus, indem Sie F5 drücken oder "Debuggen > starten" auswählen. Die Datenbank sollte automatisch mit einer Datei mit dem Namen products.dberstellt werden. Geben Sie einen Kategorienamen ein, und drücken Sie die EINGABETASTE, und fügen Sie dann Produkte zum unteren Raster hinzu. Klicken Sie auf "Speichern", und beobachten Sie die Rasteraktualisierung mit den bereitgestellten IDs der Datenbank. Markieren Sie eine Zeile und drücken Sie Entf, um die Zeile zu entfernen. Die Entität wird gelöscht, wenn Sie auf "Speichern" klicken.

Ausführen der Anwendung

Eigenschaftsänderungsbenachrichtigung

In diesem Beispiel werden vier Schritte verwendet, um die Entitäten mit der Benutzeroberfläche zu synchronisieren.

  1. Der anfängliche Aufruf _context.Categories.Load() lädt die Kategoriendaten.
  2. Die Lazy-Loading-Proxies laden die Daten zu den abhängigen Produkten.
  3. Die integrierte Änderungsnachverfolgung von EF Core führt bei Aufruf von _context.SaveChanges() die notwendigen Anpassungen an Entitäten durch, einschließlich Einfügungen und Löschungen.
  4. Die Aufrufe zu DataGridView.Items.Refresh() führen zu einem Neuladen mit den neu generierten IDs.

Dies funktioniert für unser Beispiel für die ersten Schritte, aber Sie benötigen möglicherweise zusätzlichen Code für andere Szenarien. WPF-Steuerelemente rendern die Benutzeroberfläche, indem die Felder und Eigenschaften von Ihren Entitäten gelesen werden. Wenn Sie einen Wert auf der Benutzeroberfläche bearbeiten, wird dieser Wert an Ihre Entität übergeben. Wenn Sie den Wert einer Eigenschaft direkt in Ihrer Entität ändern, z. B. das Laden aus der Datenbank, spiegelt WPF nicht sofort die Änderungen in der Benutzeroberfläche wider. Das Renderingmodul muss über die Änderungen benachrichtigt werden. Das Projekt hat dies durch das manuelle Aufrufen von Refresh()getan. Eine einfache Möglichkeit zum Automatisieren dieser Benachrichtigung ist die Implementierung der INotifyPropertyChanged-Schnittstelle . WPF-Komponenten erkennen die Schnittstelle automatisch und registrieren sich für Änderungsereignisse. Die Entität ist für das Auslösen dieser Ereignisse verantwortlich.

Tipp

Weitere Informationen zum Behandeln von Änderungen finden Sie unter: Implementieren von Benachrichtigungen zur Änderung von Eigenschaften.

Nächste Schritte

Erfahren Sie mehr über das Konfigurieren eines DbContext-