Udostępnij za pośrednictwem


Wprowadzenie do 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 (główna\główna) i Produkt (podrzędny\szczegółowy). 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.

Wymagania wstępne

Aby ukończyć ten przewodnik, potrzebujesz zainstalowanego programu Visual Studio 2019 w wersji 16.3 lub nowszej z wybranym pakietem funkcji pulpitu .NET. Aby uzyskać więcej informacji na temat instalowania najnowszej wersji programu Visual Studio, zobacz Install 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.

Zainstaluj pakiety NuGet programu Entity Framework

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

    Zarządzanie pakietami NuGet

  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

    Pakiet Sqlite

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

Uwaga / Notatka

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 encji podczas działania, co obejmuje wypełnianie obiektów danymi z bazy danych, śledzenie zmian i zapisywanie danych w niej.

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 sekcji Konfigurowanie DbContext.
  • W tym przykładzie użyto OnConfiguring nadpisania w celu określenia pliku danych Sqlite.
  • Wywołanie UseLazyLoadingProxies informuje EF Core o zaimplementowaniu ładowania leniwego, więc obiekty podrzędne są automatycznie ładowane, gdy są uzyskiwane z elementu nadrzędnego.

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

Wskazówka

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

Leniwe ładowanie

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 "Plain Old C# Object" (POCO), program EF Core osiąga opóźnione ładowanie, tworząc podczas wykonywania programu wystąpienia pochodnych typów proxy i następnie przesłaniając właściwości wirtualne w Twoich klasach w celu dodania mechanizmu ładowania. Aby uzyskać leniwe ładowanie powiązanych obiektów, właściwości nawigacyjne muszą być zadeklarowane jako publiczne i wirtualne (Overridable w Visual Basic), a klasa nie może być zapieczętowana (NotOverridable w Visual Basic). W przypadku korzystania z Database First właściwości nawigacji są automatycznie tworzone jako wirtualne, aby umożliwić leniwe ładowanie.

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 Eksploratorze 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 otwierającym tagu 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 elementu Grid, za elementem kategorii 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:

Zrzut ekranu przedstawiający projektanta WPF

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 Zdarzenia w prawym górnym rogu, a następnie kliknij dwukrotnie pole tekstowe znajdujące się na prawo od etykiety Loaded.

    Właściwości okna głównego

Spowoduje to przejście do kodu związanego z formularzem. Teraz edytujemy kod, aby użyć 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. Metoda Dispose() w wystąpieniu ProductContext jest następnie wywoływana z przesłoniętej metody OnClosing. 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 / Notatka

Kod używa wywołania do EnsureCreated() zbudowania bazy danych przy 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 metod Load i SaveChanges.

Testowanie aplikacji WPF

Skompiluj i uruchom aplikację, naciskając 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 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.

Uruchamianie aplikacji

Powiadomienie o zmianie własnoś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łane.
  4. Wywołania DataGridView.Items.Refresh() wymuszają ponowne ładowanie przy użyciu nowo wygenerowanych identyfikatorów.

To działa w przypadku naszego przykładowego projektu startowego, ale w innych scenariuszach może być potrzebny dodatkowy kod. Kontrolki WPF renderują interfejs użytkownika, odczytując pola i właściwości bytów. 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. Projekt wykonał to poprzez ręczne wywołanie 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ń.

Wskazówka

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

Dalsze kroki

Dowiedz się więcej na temat konfigurowania DbContext.