Partager via


Bien démarrer avec WPF

Cette procédure pas à pas montre comment lier des types POCO aux contrôles WPF dans un formulaire « principal-détail ». L’application utilise les API Entity Framework pour remplir des objets avec des données de la base de données, suivre les modifications et conserver les données dans la base de données.

Le modèle définit deux types qui participent à une relation un-à-plusieurs : Catégorie (principal\main) et Product (dépendant\detail). L’infrastructure de liaison de données WPF permet la navigation entre les objets associés : la sélection de lignes dans l’affichage maître entraîne la mise à jour de la vue des détails avec les données enfants correspondantes.

Les captures d’écran et les listes de code de cette procédure pas à pas sont extraites de Visual Studio 2019 16.6.5.

Tip

Vous pouvez afficher l'exemple de cet article sur GitHub.

Pre-Requisites

Visual Studio 2019 16.3 ou version ultérieure doit être installé avec la charge de travail de bureau .NET sélectionnée pour effectuer cette procédure pas à pas. Pour plus d’informations sur l’installation de la dernière version de Visual Studio, consultez Installer Visual Studio.

Créer l’application

  1. Ouvrez Visual Studio.
  2. Dans la fenêtre de démarrage, choisissez Créer un projet.
  3. Recherchez « WPF », choisissez Application WPF (.NET), puis choisissez Suivant.
  4. À l’écran suivant, donnez un nom au projet, par exemple GetStartedWPF, puis choisissez Créer.

Installer les packages NuGet de Entity Framework

  1. Cliquez avec le bouton droit sur la solution et choisissez Gérer les packages NuGet pour la solution...

    Gérer les packages NuGet

  2. Tapez entityframeworkcore.sqlite dans la zone de recherche.

  3. Sélectionnez le package Microsoft.EntityFrameworkCore.Sqlite .

  4. Vérifiez le projet dans le volet droit, puis cliquez sur Installer

    Sqlite Package

  5. Répétez les étapes pour rechercher entityframeworkcore.proxies et installer Microsoft.EntityFrameworkCore.Proxies.

Note

Lorsque vous avez installé le package Sqlite, il a automatiquement extrait le package de base Microsoft.EntityFrameworkCore associé. Le package Microsoft.EntityFrameworkCore.Proxies prend en charge les données « lazy-loading ». Cela signifie que lorsque vous avez des entités avec des entités enfants, seuls les parents sont récupérés lors de la charge initiale. Les proxys détectent lorsqu’une tentative d’accès aux entités enfants est effectuée et les charge automatiquement à la demande.

Définir un modèle

Dans cette procédure pas à pas, vous allez implémenter un modèle à l’aide de « code en premier ». Cela signifie qu’EF Core crée les tables de base de données et le schéma en fonction des classes C# que vous définissez.

Ajoutez une nouvelle classe. Donnez-lui le nom : Product.cs et remplissez-le comme suit :

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

Ensuite, ajoutez une classe nommée Category.cs et remplissez-la avec le code suivant :

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

La propriété Products sur la classe Category et la propriété Category de la classe Product sont des propriétés de navigation. Dans Entity Framework, les propriétés de navigation permettent de parcourir une relation entre deux types d’entités.

En plus de définir des entités, vous devez définir une classe qui dérive de DbContext et expose les propriétés TEntity< DbSet>. Les propriétés TEntity< DbSet>indiquent au contexte quels types vous souhaitez inclure dans le modèle.

Une instance du type dérivé DbContext gère les objets d’entité pendant l’exécution, ce qui inclut la remplissage d’objets avec des données d’une base de données, le suivi des modifications et la persistance des données dans la base de données.

Ajoutez une nouvelle ProductContext.cs classe au projet avec la définition suivante :

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();
        }
    }
}
  • L’instruction DbSet indique à EF Core quelles entités C# doivent être mappées à la base de données.
  • Il existe différentes façons de configurer EF Core DbContext. Vous pouvez en savoir plus sur ces éléments : configuration d’un dbContext.
  • Cet exemple utilise le OnConfiguring remplacement pour spécifier un fichier de données Sqlite.
  • L’appel UseLazyLoadingProxies indique à EF Core d’implémenter le chargement différé, de sorte que les entités enfants sont automatiquement chargées lors de l’accès à partir du parent.

Appuyez sur Ctrl+Maj+B ou accédez à Build > Solution pour compiler le projet.

Tip

Découvrez les différentes manières de garder votre base de données et vos modèles EF Core synchronisés : Gestion des schémas de base de données.

Chargement différé

La propriété Products sur la classe Category et la propriété Category de la classe Product sont des propriétés de navigation. Dans Entity Framework Core, les propriétés de navigation permettent de parcourir une relation entre deux types d’entités.

EF Core vous offre la possibilité de charger automatiquement des entités associées à partir de la base de données la première fois que vous accédez à la propriété de navigation. Avec ce type de chargement (appelé chargement différé), sachez que la première fois que vous accédez à chaque propriété de navigation, une requête distincte sera exécutée sur la base de données si le contenu n’est pas déjà dans le contexte.

Lorsque vous utilisez des types d’entités « Objet C# traditionnel » (POCO), EF Core recourt à un chargement différé en créant des instances de types proxy dérivés pendant l’exécution, puis en remplaçant les propriétés virtuelles dans vos classes pour ajouter le hook de chargement. Pour activer le chargement différé (lazy loading) des objets liés, vous devez déclarer les accesseurs (getters) des propriétés de navigation comme public et virtual (Overridable en Visual Basic), et votre classe ne doit pas être sealed (NotOverridable en Visual Basic). Lorsque vous utilisez Database First, les propriétés de navigation sont automatiquement rendues virtuelles pour activer le chargement différé.

Lier un objet à des contrôles

Ajoutez les classes définies dans le modèle en tant que sources de données pour cette application WPF.

  1. Double-cliquez sur MainWindow.xaml dans l’Explorateur de solutions pour ouvrir le formulaire principal

  2. Choisissez l’onglet XAML pour modifier le code XAML.

  3. Immédiatement après la balise d’ouverture Window , ajoutez les sources suivantes pour vous connecter aux entités 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. Cette opération configure la source pour les catégories « parent » et la deuxième source pour les produits « détail ».

  5. Ensuite, ajoutez le balisage suivant à votre code XAML après la balise d’ouverture 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. Notez que la CategoryId valeur est définie ReadOnly car elle est affectée par la base de données et ne peut pas être modifiée.

Ajout d’une grille de détails

Maintenant que la grille existe pour afficher des catégories, la grille de détails peut être ajoutée pour afficher les produits. Ajoutez-le à l’intérieur de l’élément Grid , après l’élément 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>

Enfin, ajoutez un bouton Save et connectez l'événement de clic à Button_Click.

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

Votre vue de conception doit ressembler à ceci :

Capture d’écran du concepteur WPF

Ajouter du code qui gère l’interaction des données

Il est temps d’ajouter des gestionnaires d’événements à la fenêtre principale.

  1. Dans la fenêtre XAML, cliquez sur l’élément <Window> pour sélectionner la fenêtre principale.

  2. Dans la fenêtre Propriétés , choisissez Événements en haut à droite, puis double-cliquez sur la zone de texte à droite de l’étiquette Chargée .

    Propriétés de la fenêtre principale

Cela vous amène au code source lié au formulaire. Nous allons maintenant modifier le code pour utiliser ProductContext afin d'accéder aux données. Mettez à jour le code comme indiqué ci-dessous.

Le code déclare une instance longue durée de ProductContext. L’objet ProductContext est utilisé pour interroger et enregistrer des données dans la base de données. La méthode Dispose() sur l’instance ProductContext est ensuite appelée à partir de la méthode surchargée OnClosing. Les commentaires de code expliquent ce que fait chaque étape.

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

Note

Le code utilise un appel à EnsureCreated() pour générer la base de données à la première exécution. Cela est acceptable pour les démonstrations, mais dans les applications de production, vous devez examiner les migrations pour gérer votre schéma. Le code s’exécute également de manière synchrone, car il utilise une base de données SQLite locale. Pour les scénarios de production qui impliquent généralement un serveur distant, envisagez d'utiliser les versions asynchrones des méthodes Load et SaveChanges.

Tester l’application WPF

Compilez et exécutez l’application en appuyant sur F5 ou en choisissant Démarrer > le débogage. La base de données doit être créée automatiquement avec un fichier nommé products.db. Entrez un nom de catégorie et appuyez sur Entrée, puis ajoutez des produits à la grille inférieure. Cliquez sur Enregistrer et regardez l’actualisation de la grille avec les ID fournis par la base de données. Mettez en surbrillance une ligne et appuyez sur Supprimer pour supprimer la ligne. L’entité est supprimée lorsque vous cliquez sur Enregistrer.

Exécution de l’application

Notification de modification de propriété

Cet exemple s’appuie sur quatre étapes pour synchroniser les entités avec l’interface utilisateur.

  1. L’appel _context.Categories.Load() initial charge les données des catégories.
  2. Les proxys de chargement différé chargent les données des produits dépendants.
  3. Le suivi intégré des modifications d’EF Core applique les modifications nécessaires aux entités, y compris les insertions et les suppressions, lorsqu’on appelle _context.SaveChanges().
  4. Les appels à DataGridView.Items.Refresh() forcent un rechargement avec les identifiants nouvellement générés.

Cela fonctionne pour notre exemple de prise en main, mais vous pouvez nécessiter du code supplémentaire pour d’autres scénarios. Les contrôles WPF restituent l’interface utilisateur en lisant les champs et les propriétés sur vos entités. Lorsque vous modifiez une valeur dans l’interface utilisateur, cette valeur est transmise à votre entité. Lorsque vous modifiez la valeur d’une propriété directement sur votre entité, telle que le chargement à partir de la base de données, WPF ne reflète pas immédiatement les modifications apportées à l’interface utilisateur. Le moteur de rendu doit être informé des modifications. Le projet a fait ceci en appelant Refresh() manuellement. Un moyen simple d’automatiser cette notification consiste à implémenter l’interface INotifyPropertyChanged . Les composants WPF détectent automatiquement l’interface et s’inscrivent pour les événements de modification. L’entité est chargée de déclencher ces événements.

Tip

Pour en savoir plus sur la gestion des modifications, lisez : Comment implémenter la notification de modification de propriété.

Étapes suivantes

En savoir plus sur la configuration d’un dbContext.