Bien démarrer avec WPF
Cette procédure pas à pas montre comment lier des types POCO à des contrôles WPF dans un formulaire « principal-détail ». L’application utilise les API Entity Framework pour remplir les objets avec des données de la base de données, effectuer le suivi des 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 : Category (Catégorie) (principal\« main ») et Product (Produit) (dépendant\détail). Le framework de liaison de données WPF permet la navigation entre les objets associés : la sélection de lignes dans l’affichage principal entraîne la mise à jour de la vue des détails avec les données enfants correspondantes.
Les captures d’écran et les parties de code de cette procédure pas à pas sont extraites de Visual Studio 2019 16.6.5.
Conseil
Vous pouvez afficher cet exemple sur GitHub.
Conditions préalables
Pour effectuer cette procédure pas à pas, vous devez avoir Visual Studio 2019 16.3 ou ultérieur installé avec la charge de travail .NET Desktop sélectionnée. Pour plus d’informations sur l’installation de la dernière version de Visual Studio, consultez Installer Visual Studio.
Création de l’application
- Ouvrez Visual Studio.
- Dans la fenêtre de démarrage, choisissez Créer un projet.
- Recherchez « WPF », choisissez Application WPF (.NET Core), puis choisissez Suivant.
- Sur l’écran suivant, nommez le projet, par exemple GetStartedWPF, puis choisissez Créer.
Installer le package NuGet Entity Framework
Cliquez avec le bouton droit sur la solution, puis choisissez Gérer les packages NuGet pour la solution...
Tapez
entityframeworkcore.sqlite
dans la zone de recherche.Sélectionnez le package Microsoft.EntityFrameworkCore.Sqlite.
Cochez le projet dans le volet droit, puis cliquez sur Installer
Répétez les étapes pour rechercher
entityframeworkcore.proxies
et installer Microsoft.EntityFrameworkCore.Proxies.
Remarque
Quand vous avez installé le package SQLite, il a extrait automatiquement le package de base Microsoft.EntityFrameworkCore associé. Le package Microsoft.EntityFrameworkCore.Proxies prend en charge les données « à chargement différé ». Cela signifie que quand vous avez des entités avec des entités enfants, seuls les parents sont extraits lors du chargement initial. Les proxys détectent quand une tentative d’accès aux entités enfants est effectuée et les chargent automatiquement à la demande.
Définir un modèle
Dans cette procédure pas à pas, vous allez implémenter un modèle en utilisant l’approche « Code First » (Code en premier). Cela signifie qu’EF Core va créer les tables et le schéma de la base de données d’après les classes C# que vous définissez.
Ajoutez une nouvelle classe. Nommez-la Product.cs
et remplissez-la 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 offrent un moyen 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 DbSet<TEntity>. Les propriétés DbSet<TEntity> indiquent au contexte quels types vous voulez inclure dans le modèle.
Une instance du type dérivé de DbContext gère les objets d’entité au moment de l’exécution, ce qui comprend le remplissage des objets avec les données d’une base de données, le suivi des modifications et la conservation des données dans la base de données.
Ajoutez une nouvelle classe ProductContext.cs
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();
}
}
}
DbSet
informe EF Core des entités C# qui doivent être mappées à la base de données.- Il existe plusieurs façons de configurer le
DbContext
d’EF Core. Vous pouvez en savoir plus sur ceux-ci dans : Configuration d’un DbContext. - Cet exemple utilise le remplacement de
OnConfiguring
pour spécifier un fichier de données SQLite. - L’appel de
UseLazyLoadingProxies
indique à EF Core d’implémenter le chargement différé, de sorte que les entités enfants sont chargées automatiquement quand leur accès est effectué depuis le parent.
Appuyez sur Ctrl+Maj+B ou accédez à Générer> Générer la solution pour compiler le projet.
Conseil
Découvrez les différents moyens de maintenir la synchronisation de votre base de données et de vos modèles EF Core : 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 offrent un moyen de naviguer dans une relation entre deux types d’entités.
EF Core vous donne la possibilité de charger automatiquement des entités associées depuis 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é »), notez bien que la première fois que vous accédez à chaque propriété de navigation, une requête distincte va être exécutée sur la base de données si le contenu n’est pas déjà dans le contexte.
Quand vous utilisez des types d’entités POCO (« Plain Old C# Object »), EF Core effectue un chargement différé en créant des instances de types proxy dérivés lors de l’exécution, puis en remplaçant les propriétés virtuelles dans vos classes pour ajouter le raccordement de chargement. Pour obtenir le chargement différé d’objets associés, vous devez déclarer les getters de propriété de navigation en tant que public et virtual (Overridable dans Visual Basic), et votre classe ne doit pas être scellée (NotOverridable dans Visual Basic). Quand vous utilisez l’approche 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.
Double-cliquez sur MainWindow.xaml dans l’Explorateur de solutions pour ouvrir le formulaire principal.
Choisissez l’onglet XAML pour modifier le code XAML.
Immédiatement après la balise ouvrante
Window
, ajoutez les sources suivantes à 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>
Ceci configure la source pour les catégories « parent » et la deuxième source pour les produits « detail ».
Ensuite, ajoutez le balisage suivant à votre code XAML après la balise ouvrante
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>
Notez que la
CategoryId
est définie surReadOnly
, 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 les catégories, la grille de détails peut être ajoutée pour afficher les produits. Ajoutez-la à l’intérieur de l’élément Grid
, après l’élément DataGrid
des catégories.
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 :
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.
Dans la fenêtre XAML, cliquez sur l’élément <Window> pour sélectionner la fenêtre principale.
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é.
Ceci vous amène au code-behind du formulaire ; nous allons maintenant modifier le code de façon à utiliser ProductContext
pour effectuer l’accès aux données. Mettez à jour le code comme indiqué ci-dessous.
Le code déclare une instance d’exécution longue 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 de ProductContext
est ensuite appelée depuis la méthode OnClosing
remplacée. 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);
}
}
}
Remarque
Le code utilise un appel à EnsureCreated()
pour créer la base de données lors de la première exécution. Ceci est acceptable pour des démonstrations, mais dans les applications de production, vous devez examiner migrations pour gérer votre schéma. Le code s’exécute également de façon 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 sélectionnant Déboguer> 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 dans la grille du bas. Cliquez sur enregistrer et observez l’actualisation de la grille avec les ID fournis par la base de données. Mettez une ligne en surbrillance et appuyez sur Delete (Supprimer) pour supprimer la ligne. L’entité est supprimée quand vous cliquez sur Save (Enregistrer).
Notification de modification de propriété
Cet exemple s’appuie sur quatre étapes pour synchroniser les entités avec l’interface utilisateur.
- L’appel initial
_context.Categories.Load()
charge les données des catégories. - Les proxys de chargement différé chargent les données des produits dépendants.
- Le suivi des modifications intégré d’EF Core apporte les modifications nécessaires aux entités, y compris les insertions et les suppressions, quand
_context.SaveChanges()
est appelé. - Les appels à
DataGridView.Items.Refresh()
forcent un rechargement avec les ID nouvellement générés.
Ceci fonctionne pour notre exemple de prise en main, mais vous pouvez avoir besoin de code supplémentaire pour d’autres scénarios. Les contrôles WPF rendent l’interface utilisateur en lisant les champs et les propriétés sur vos entités. Quand vous modifiez une valeur dans l’interface utilisateur, cette valeur est passée à votre entité. Quand vous modifiez la valeur d’une propriété directement sur votre entité, comme la charger depuis la base de données, WPF ne reflète pas immédiatement les modifications dans l’interface utilisateur. Le moteur de rendu doit être informé des modifications. Le projet fait cela via un appel manuel de Refresh()
. 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.
Conseil
Pour en savoir plus sur la gestion des modifications, consultez Comment implémenter la notification des modifications des propriétés.
Étapes suivantes
En savoir plus sur la Configuration d’un DbContext.