Začínáme s WPF

Tento podrobný návod ukazuje, jak svázat typy POCO s ovládacími prvky WPF ve formuláři "main-detail". Aplikace používá rozhraní API entity Framework k naplnění objektů daty z databáze, sledování změn a zachování dat do databáze.

Model definuje dva typy, které se účastní relace 1:N: Category (principal\main) a Product (závislý\detail). Architektura datových vazeb WPF umožňuje navigaci mezi souvisejícími objekty: výběr řádků v hlavním zobrazení způsobí, že se zobrazení podrobností aktualizuje s odpovídajícími podřízenými daty.

Snímky obrazovky a výpisy kódu v tomto návodu pocházejí ze sady Visual Studio 2019 16.6.5.

Tip

Ukázku pro tento článek najdete na GitHubu.

Předpoklady

K dokončení tohoto návodu musíte mít nainstalovanou sadu Visual Studio 2019 16.3 nebo novější s vybranou úlohou .NET Desktop. Další informace o instalaci nejnovější verze sady Visual Studio naleznete v tématu Instalace sady Visual Studio.

Vytvoření aplikace

  1. Otevřete sadu Visual Studio.
  2. V úvodním okně zvolte Vytvořit nový projekt.
  3. Vyhledejte WPF, zvolte WPF App (.NET Core) a pak zvolte Další.
  4. Na další obrazovce zadejte název projektu, například GetStartedWPF, a zvolte Vytvořit.

Instalace balíčků NuGet entity Framework

  1. Klikněte pravým tlačítkem na řešení a zvolte Spravovat balíčky NuGet pro řešení...

    Manage NuGet Packages

  2. Zadejte entityframeworkcore.sqlite do vyhledávacího pole.

  3. Vyberte balíček Microsoft.EntityFrameworkCore.Sqlite.

  4. Zkontrolujte projekt v pravém podokně a klikněte na Nainstalovat.

    Sqlite Package

  5. Opakováním kroků vyhledejte entityframeworkcore.proxies a nainstalujte Microsoft.EntityFrameworkCore.Proxies.

Poznámka

Když jste nainstalovali balíček Sqlite, automaticky se stáhl související základní balíček Microsoft.EntityFrameworkCore . Balíček Microsoft.EntityFrameworkCore.Proxies poskytuje podporu pro opožděná načítání dat. To znamená, že pokud máte entity s podřízenými entitami, při počátečním zatížení se načtou pouze nadřazené entity. Proxy servery zjistí, kdy se provede pokus o přístup k podřízeným entitám, a automaticky je načte na vyžádání.

Definování modelu

V tomto názorném postupu implementujete model pomocí kódu jako první. To znamená, že EF Core vytvoří databázové tabulky a schéma založené na definovaných třídách jazyka C#.

Přidejte novou třídu. Pojmenujte ho takto: Product.cs

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

Dále přidejte třídu s názvem Category.cs a naplňte ji následujícím kódem:

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

Vlastnost Products ve třídě Category a Category ve třídě Product jsou navigační vlastnosti. Ve službě Entity Framework poskytují navigační vlastnosti způsob, jak přecházet mezi dvěma typy entit.

Kromě definování entit je nutné definovat třídu, která je odvozena z DbContext a zveřejňuje DbSet<TEntity> vlastnosti. Vlastnosti DbSet<TEntity> znají kontext, které typy chcete zahrnout do modelu.

Instance odvozeného typu DbContext spravuje objekty entity během doby běhu, což zahrnuje naplnění objektů dat z databáze, sledování změn a zachování dat do databáze.

Přidejte do projektu novou ProductContext.cs třídu s následující definicí:

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();
        }
    }
}
  • Informuje DbSet EF Core, jaké entity jazyka C# by se měly namapovat na databázi.
  • Existují různé způsoby konfigurace EF Core DbContext. O nich si můžete přečíst v tématu Konfigurace DbContext.
  • Tento příklad používá přepsání OnConfiguring k určení datového souboru Sqlite.
  • Volání UseLazyLoadingProxies říká EF Core, aby implementoval opožděné načítání, takže podřízené entity se při přístupu z nadřazeného objektu automaticky načtou.

Stisknutím kombinace kláves CTRL+SHIFT+B nebo přejděte do sestavení > řešení sestavení a zkompilujte projekt.

Tip

Seznamte se s tím rozdílem, že se databáze a modely EF Core synchronizují: Správa schémat databáze.

Opožděné načítání

Vlastnost Products ve třídě Category a Category ve třídě Product jsou navigační vlastnosti. V Entity Framework Core poskytují navigační vlastnosti způsob, jak procházet vztah mezi dvěma typy entit.

EF Core nabízí možnost načíst související entity z databáze automaticky při prvním přístupu k navigační vlastnosti. U tohoto typu načítání (označovaného jako opožděné načítání) mějte na paměti, že při prvním přístupu ke každé navigační vlastnosti se vůči databázi spustí samostatný dotaz, pokud obsah ještě není v kontextu.

Pokud používáte entity "Plain Old C# Object" (POCO), EF Core dosahuje opožděného načítání vytvořením instancí odvozených typů proxy během běhu a přepsáním virtuálních vlastností ve vašich třídách, aby se přidalo načítání háku. Chcete-li získat opožděné načítání souvisejících objektů, musíte deklarovat navigační vlastnost getters jako veřejné a virtuální (Overridable v jazyce Visual Basic) a vaše třída nesmí být zapečetěná (NotOverridable v jazyce Visual Basic). Při použití funkce Database First se navigační vlastnosti automaticky vytvoří jako virtuální, aby se povolilo opožděné načítání.

Vytvoření vazby objektu k ovládacím prvkům

Přidejte třídy definované v modelu jako zdroje dat pro tuto aplikaci WPF.

  1. Poklikáním na MainWindow.xaml v Průzkumník řešení otevřete hlavní formulář.

  2. Zvolte kartu XAML a upravte xaml.

  3. Hned za počáteční Window značku přidejte následující zdroje pro připojení k entitě 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. Tím se nastaví zdroj pro "nadřazené" kategorie a druhý zdroj pro produkty s podrobnostmi.

  5. Dále přidejte následující kód do xaml za počáteční Grid značku.

    <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. Všimněte si, že CategoryId je nastavená, ReadOnly protože je přiřazena databází a nelze ji změnit.

Přidání mřížky podrobností

Teď, když mřížka existuje pro zobrazení kategorií, je možné přidat mřížku podrobností, aby se zobrazily produkty. Přidejte tento prvek do elementu Grid za element 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>

Nakonec do události Button_Clickkliknutí přidejte Save tlačítko a drát .

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

Vaše návrhové zobrazení by mělo vypadat takto:

Screenshot of WPF Designer

Přidání kódu, který zpracovává interakci s daty

Je čas přidat některé obslužné rutiny událostí do hlavního okna.

  1. V okně XAML klikněte na <prvek Okno> a vyberte hlavní okno.

  2. V okně Vlastnosti zvolte Události v pravém horním rohu a potom poklikejte na textové pole napravo od načteného popisku.

    Main Window Properties

Tím se dostanete na kód formuláře, který teď upravíme tak, aby používal ProductContext přístup k datům. Aktualizujte kód, jak je znázorněno níže.

Kód deklaruje dlouho běžící instanci ProductContext. Objekt ProductContext se používá k dotazování a ukládání dat do databáze. Metoda Dispose() v ProductContext instanci se pak volá z přepsáné OnClosing metody. Komentáře ke kódu vysvětlují, co každý krok dělá.

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

Poznámka

Kód používá volání k EnsureCreated() sestavení databáze při prvním spuštění. To je přijatelné pro ukázky, ale v produkčních aplikacích byste se měli podívat na migrace pro správu schématu. Kód se také spouští synchronně, protože používá místní databázi SQLite. V produkčních scénářích, které obvykle zahrnují vzdálený server, zvažte použití asynchronních Load verzí a SaveChanges metod.

Testování aplikace WPF

Zkompilujte a spusťte aplikaci stisknutím klávesy F5 nebo výběrem možnosti Spustit > ladění ladění. Databáze by se měla automaticky vytvořit pomocí souboru s názvem products.db. Zadejte název kategorie a stiskněte Enter a pak přidejte produkty do dolní mřížky. Klikněte na Uložit a sledujte aktualizaci mřížky s ID zadanou databází. Zvýrazněte řádek a stisknutím klávesy Odstranit odeberte řádek. Entita bude odstraněna po kliknutí na tlačítko Uložit.

Running application

Oznámení o změně vlastnosti

Tento příklad využívá čtyři kroky k synchronizaci entit s uživatelským rozhraním.

  1. Počáteční volání _context.Categories.Load() načte data kategorií.
  2. Opožděné načítání proxy serverů načítá data závislých produktů.
  3. Integrované sledování změn EF Core provádí potřebné úpravy entit, včetně vkládání a odstraňování, když _context.SaveChanges() se volá.
  4. Volání k DataGridView.Items.Refresh() vynucení opětovného načtení s nově vygenerovanými ID.

To funguje pro naši ukázku Začínáme, ale pro jiné scénáře můžete vyžadovat další kód. Ovládací prvky WPF vykreslují uživatelské rozhraní čtením polí a vlastností entit. Při úpravě hodnoty v uživatelském rozhraní (UI) se tato hodnota předá vaší entitě. Když změníte hodnotu vlastnosti přímo na entitě, například ji načítáte z databáze, WPF okamžitě neodráží změny v uživatelském rozhraní. Vykreslovací modul musí být informován o změnách. Projekt to udělal ručním voláním Refresh(). Snadný způsob, jak toto oznámení automatizovat, je implementace INotifyPropertyChanged rozhraní. Komponenty WPF automaticky rozpozná rozhraní a zaregistrují události změn. Entita zodpovídá za vyvolání těchto událostí.

Tip

Další informace o tom, jak zpracovávat změny, najdete v tématu: Jak implementovat oznámení o změně vlastnosti.

Další kroky

Přečtěte si další informace o konfiguraci DbContext.