Wprowadzenie do korzystania z platformy WPF

W tym przewodniku krok po kroku pokazano, jak powiązać typy POCO z kontrolkami WPF w formularzu "main-detail". Aplikacja używa interfejsów API programu Entity Framework do wypełniania obiektów danymi z bazy danych, śledzenia zmian i utrwalania danych w bazie danych.

Model definiuje dwa typy, które uczestniczą w relacji jeden do wielu: Kategoria (principal\main) i Product (zależne\szczegóły). Struktura powiązania danych WPF umożliwia nawigację między powiązanymi obiektami: wybieranie wierszy w widoku głównym powoduje, że widok szczegółów zostanie zaktualizowany przy użyciu odpowiednich danych podrzędnych.

Zrzuty ekranu i listy kodu w tym przewodniku pochodzą z programu Visual Studio 2019 16.6.5.

Napiwek

Przykład z tego artykułu można zobaczyć w witrynie GitHub.

Wymagania wstępne

Aby ukończyć ten przewodnik, musisz mieć zainstalowany program Visual Studio 2019 w wersji 16.3 lub nowszej z obciążeniem pulpitu platformy .NET. Aby uzyskać więcej informacji na temat instalowania najnowszej wersji programu Visual Studio, zobacz Instalowanie programu Visual Studio.

Tworzenie aplikacji

  1. Otwórz program Visual Studio.
  2. W oknie uruchamiania wybierz pozycję Utwórz nowy projekt.
  3. Wyszukaj ciąg "WPF", wybierz pozycję Aplikacja WPF (.NET Core), a następnie wybierz pozycję Dalej.
  4. Na następnym ekranie nadaj projektowi nazwę, na przykład GetStartedWPF, a następnie wybierz pozycję Utwórz.

Instalowanie pakietów NuGet programu Entity Framework

  1. Kliknij rozwiązanie prawym przyciskiem myszy i wybierz polecenie Zarządzaj pakietami NuGet dla rozwiązania...

    Manage NuGet Packages

  2. Wpisz entityframeworkcore.sqlite w polu wyszukiwania.

  3. Wybierz pakiet Microsoft.EntityFrameworkCore.Sqlite.

  4. Sprawdź projekt w okienku po prawej stronie i kliknij pozycję Zainstaluj

    Sqlite Package

  5. Powtórz kroki, aby wyszukać entityframeworkcore.proxies i zainstalować pliki Microsoft.EntityFrameworkCore.Proxies.

Uwaga

Po zainstalowaniu pakietu Sqlite automatycznie ściągnął powiązany pakiet podstawowy Microsoft.EntityFrameworkCore . Pakiet Microsoft.EntityFrameworkCore.Proxies zapewnia obsługę danych "ładowanych z opóźnieniem". Oznacza to, że jeśli masz jednostki z jednostkami podrzędnymi, tylko elementy nadrzędne są pobierane podczas początkowego ładowania. Serwery proxy wykrywają, kiedy jest podejmowana próba uzyskania dostępu do jednostek podrzędnych i automatycznie ładuje je na żądanie.

Definiowanie modelu

W tym przewodniku zaimplementujesz model przy użyciu polecenia "code first". Oznacza to, że program EF Core utworzy tabele i schemat bazy danych na podstawie zdefiniowanych klas języka C#.

Dodaj nową klasę. Nadaj jej nazwę: Product.cs i wypełnij ją następująco:

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; }
    }
}

Następnie dodaj klasę o nazwie Category.cs i wypełnij ją następującym kodem:

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>();
    }
}

Właściwość Products w klasie Category i właściwości Category w klasie Product to właściwości nawigacji. W programie Entity Framework właściwości nawigacji zapewniają sposób nawigowania po relacji między dwoma typami jednostek.

Oprócz definiowania jednostek należy zdefiniować klasę pochodzącą z elementu DbContext i uwidaczniać właściwości TEntity> zestawu dbSet<. Właściwości DbSet<TEntity> poinformują kontekst o typach, które mają zostać uwzględnione w modelu.

Wystąpienie typu pochodnego DbContext zarządza obiektami jednostki w czasie wykonywania, co obejmuje wypełnianie obiektów danymi z bazy danych, śledzenie zmian i utrwalanie danych do bazy danych.

Dodaj nową ProductContext.cs klasę do projektu z następującą definicją:

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();
        }
    }
}
  • Narzędzie DbSet informuje platformę EF Core o tym, jakie jednostki języka C# powinny być mapowane na bazę danych.
  • Istnieje wiele sposobów konfigurowania programu EF Core DbContext. Informacje o nich można znaleźć w temacie Konfigurowanie elementu DbContext.
  • W tym przykładzie użyto OnConfiguring przesłonięcia w celu określenia pliku danych sqlite.
  • Wywołanie UseLazyLoadingProxies informuje program EF Core o zaimplementowaniu ładowania leniwego, więc jednostki podrzędne są automatycznie ładowane po korzystaniu z elementu nadrzędnego.

Naciśnij klawisze CTRL+SHIFT+B lub przejdź do pozycji Kompiluj > rozwiązanie kompilacji, aby skompilować projekt.

Napiwek

Dowiedz się więcej o tym, jak zachować synchronizację baz danych i modeli EF Core: zarządzanie schematami baz danych.

Ładowanie opóźnione

Właściwość Products w klasie Category i właściwości Category w klasie Product to właściwości nawigacji. W programie Entity Framework Core właściwości nawigacji zapewniają sposób nawigowania po relacji między dwoma typami jednostek.

Program EF Core umożliwia automatyczne ładowanie powiązanych jednostek z bazy danych przy pierwszym uzyskiwaniu dostępu do właściwości nawigacji. W przypadku tego typu ładowania (nazywanego ładowaniem leniwym) należy pamiętać, że przy pierwszym uzyskiwaniu dostępu do każdej właściwości nawigacji zostanie wykonane oddzielne zapytanie względem bazy danych, jeśli zawartość nie znajduje się jeszcze w kontekście.

W przypadku korzystania z typów jednostek "Zwykły stary obiekt C#" (POCO) program EF Core osiąga opóźnienie ładowania, tworząc wystąpienia pochodnych typów serwerów proxy w czasie wykonywania, a następnie przesłaniając właściwości wirtualne w klasach w celu dodania haka ładowania. Aby uzyskać leniwe ładowanie powiązanych obiektów, należy zadeklarować metody pobierania właściwości nawigacji jako publiczne i wirtualne (zastępowalne w Visual Basic), a klasa nie może być zapieczętowana (NotOverridable w Visual Basic). W przypadku korzystania z usługi Database First właściwości nawigacji są automatycznie tworzone jako wirtualne, aby umożliwić ładowanie leniwe.

Wiązanie obiektu z kontrolkami

Dodaj klasy zdefiniowane w modelu jako źródła danych dla tej aplikacji WPF.

  1. Kliknij dwukrotnie plik MainWindow.xaml w Eksplorator rozwiązań, aby otworzyć formularz główny

  2. Wybierz kartę XAML, aby edytować kod XAML.

  3. Natychmiast po tagu otwierania Window dodaj następujące źródła, aby nawiązać połączenie z jednostkami programu EF Core.

    <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. Spowoduje to skonfigurowanie źródła dla kategorii "nadrzędnych" i drugiego źródła dla produktów "detail".

  5. Następnie dodaj następujący znacznik do kodu XAML po tagu otwierania Grid .

    <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. Należy pamiętać, że CategoryId właściwość jest ustawiona na ReadOnly , ponieważ jest przypisana przez bazę danych i nie można jej zmienić.

Dodawanie siatki szczegółów

Teraz, gdy siatka istnieje do wyświetlania kategorii, można dodać siatkę szczegółów do wyświetlania produktów. Dodaj to wewnątrz Grid elementu po elemecie categories DataGrid .

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>

Na koniec dodaj Save przycisk i podłącz go do zdarzenia kliknięcia.Button_Click

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

Widok projektu powinien wyglądać następująco:

Screenshot of WPF Designer

Dodawanie kodu obsługującego interakcję z danymi

Nadszedł czas, aby dodać niektóre programy obsługi zdarzeń do okna głównego.

  1. W oknie XAML kliknij <element Okno> , aby wybrać okno główne.

  2. W oknie Właściwości wybierz pozycję Zdarzenia w prawym górnym rogu, a następnie kliknij dwukrotnie pole tekstowe po prawej stronie załadowanejetykiety.

    Main Window Properties

Spowoduje to wyświetlenie kodu formularza. Teraz zmodyfikujemy kod, który będzie używany ProductContext do wykonywania dostępu do danych. Zaktualizuj kod, jak pokazano poniżej.

Kod deklaruje długotrwałe wystąpienie klasy ProductContext. Obiekt ProductContext jest używany do wykonywania zapytań i zapisywania danych w bazie danych. Dispose() Metoda w wystąpieniu ProductContext jest następnie wywoływana z metody przesłoniętejOnClosing. Komentarze kodu wyjaśniają, co robi każdy krok.

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);
        }
    }
}

Uwaga

Kod używa wywołania w celu EnsureCreated() skompilowania bazy danych w pierwszym uruchomieniu. Jest to dopuszczalne w przypadku pokazów, ale w aplikacjach produkcyjnych należy przyjrzeć się migracjom w celu zarządzania schematem. Kod jest również wykonywany synchronicznie, ponieważ używa lokalnej bazy danych SQLite. W przypadku scenariuszy produkcyjnych, które zwykle obejmują serwer zdalny, rozważ użycie asynchronicznych wersji Load metod i SaveChanges .

Testowanie aplikacji WPF

Skompiluj i uruchom aplikację, naciskając klawisz F5 lub wybierając pozycję Debuguj > rozpocznij debugowanie. Baza danych powinna zostać utworzona automatycznie przy użyciu pliku o nazwie products.db. Wprowadź nazwę kategorii i naciśnij klawisz Enter, a następnie dodaj produkty do dolnej siatki. Kliknij pozycję Zapisz i obejrzyj odświeżanie siatki przy użyciu podanych identyfikatorów bazy danych. Wyróżnij wiersz i naciśnij pozycję Usuń , aby usunąć wiersz. Jednostka zostanie usunięta po kliknięciu przycisku Zapisz.

Running application

Powiadomienie o zmianie właściwości

W tym przykładzie przedstawiono cztery kroki synchronizacji jednostek z interfejsem użytkownika.

  1. Początkowe wywołanie _context.Categories.Load() ładuje dane kategorii.
  2. Leniwe serwery proxy ładują dane produktów zależnych.
  3. Wbudowane śledzenie zmian platformy EF Core wprowadza niezbędne modyfikacje jednostek, w tym wstawiania i usuwania, gdy _context.SaveChanges() jest wywoływana.
  4. Wywołania DataGridView.Items.Refresh() wymuszają ponowne ładowanie przy użyciu nowo wygenerowanych identyfikatorów.

Działa to w przypadku przykładu z wprowadzeniem, ale w innych scenariuszach może być wymagany dodatkowy kod. Kontrolki WPF renderować interfejs użytkownika, odczytując pola i właściwości jednostek. Podczas edytowania wartości w interfejsie użytkownika ta wartość jest przekazywana do jednostki. Po zmianie wartości właściwości bezpośrednio w jednostce, takiej jak załadowanie jej z bazy danych, platforma WPF nie będzie natychmiast odzwierciedlać zmian w interfejsie użytkownika. Aparat renderowania musi być powiadamiany o zmianach. W tym celu projekt został ręcznie wywołany przez wywołanie metody Refresh(). Łatwym sposobem automatyzacji tego powiadomienia jest zaimplementowanie interfejsu INotifyPropertyChanged . Składniki WPF automatycznie wykrywają interfejs i rejestrują się pod kątem zdarzeń zmiany. Jednostka jest odpowiedzialna za zgłaszanie tych zdarzeń.

Napiwek

Aby dowiedzieć się więcej o sposobie obsługi zmian, przeczytaj: Jak zaimplementować powiadomienie o zmianie właściwości.

Następne kroki 

Dowiedz się więcej na temat konfigurowania elementu DbContext.