Bien démarrer avec Windows Forms
Ce guide étape par étape montre comment construire une simple application Windows Forms (WinForms) soutenue par une base de données SQLite. L'application utilise Entity Framework Core (EF Core) pour charger les données de la base de données, suivre les changements apportés à ces données et persister ces changements dans la base de données.
Les captures d’écran et les listes de code de cette procédure pas à pas sont extraites de Visual Studio 2022 17.3.0.
Conseil
Vous pouvez afficher cet exemple sur GitHub.
Prérequis
Pour effectuer cette procédure pas à pas, vous devez avoir Visual Studio 2022 17.3 ou une version ultérieure installée avec la charge de travail de bureau .NET 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.
Choisissez l’application Windows Forms, puis choisissez Suivant.
Sur l’écran suivant, nommez le projet, par exemple, GetStartedWinForms et choisissez Suivant.
À l’écran suivant, choisissez la version .NET à utiliser. Cette procédure pas à pas a été créée avec .NET 7, mais elle doit également fonctionner avec les versions ultérieures.
Cliquez sur Créer.
Installez les packages NuGet EF Core
Cliquez avec le bouton droit sur la solution et choisissez Gérer les packages NuGet pour la solution...
Choisissez l’onglet Parcourir et recherchez « Microsoft.EntityFrameworkCore.Sqlite ».
Sélectionnez le package Microsoft.EntityFrameworkCore.Sqlite.
Vérifiez le projet GetStartedWinForms dans le volet droit.
Choisissez la version la plus récente. Pour utiliser une version préliminaire, vérifiez que la case Inclure la version préliminaire est cochée.
Cliquez sur Install.
Remarque
Microsoft.EntityFrameworkCore.Sqlite est le package « fournisseur de base de données » pour l’utilisation d’EF Core avec une base de données SQLite. Des packages similaires sont disponibles pour d'autres systèmes de base de données. L'installation d'un package de fournisseurs de bases de données apporte automatiquement toutes les dépendances nécessaires à l'utilisation d'EF Core avec ce système de base de données. Cela inclut le package de base Microsoft.EntityFrameworkCore.
Définir un modèle
Dans ce guide, nous allons mettre en œuvre un modèle en utilisant « Code First ». Cela signifie que EF Core créera les tables et le schéma de la base de données sur la base des classes C# que vous définissez. Consultez Gestion des schémas de base de données pour voir comment utiliser une base de données existante à la place.
Cliquez avec le bouton droit sur le projet, puis choisissez Ajouter, puis Classe... pour ajouter une nouvelle classe.
Utilisez le nom de fichier
Product.cs
et remplacez le code de la classe par :using System.ComponentModel; namespace GetStartedWinForms; public class Product { public int ProductId { get; set; } public string? Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } = null!; }
Répétez l’opération pour créer
Category.cs
à l'aide du code suivant :using Microsoft.EntityFrameworkCore.ChangeTracking; namespace GetStartedWinForms; public class Category { public int CategoryId { get; set; } public string? Name { get; set; } public virtual ObservableCollectionListSource<Product> Products { get; } = new(); }
La propriété Products
sur la classe Category
et la propriété Category
sur la classe Product
sont appelées « navigations ». Dans EF Core, les navigations définissent une relation entre deux types d'entités. Dans ce cas, la navigation Product.Category
fait référence à la catégorie à laquelle appartient un produit donné. De même, la navigation de collection Category.Products
contient tous les produits d’une catégorie donnée.
Conseil
Lorsque vous utilisez Windows Forms, le ObservableCollectionListSource
, qui implémente IListSource
, peut être utilisé pour les navigations de collection. Ce n'est pas nécessaire, mais cela améliore l'expérience de la liaison bidirectionnelle de données.
Définir DbContext
Dans EF Core, une classe dérivée de DbContext
est utilisée pour configurer des types d’entités dans un modèle et agir comme une session pour interagir avec la base de données. Dans le cas le plus simple, une classe DbContext
:
- Contient les propriétés
DbSet
pour chaque type d'entité du modèle. - Remplace la méthode
OnConfiguring
pour configurer le fournisseur de base de données et la chaîne de connexion à utiliser. Consultez Configurer DbContext pour plus d’informations.
Dans ce cas, la classe DbContext remplace également la méthode OnModelCreating
pour fournir des exemples de données pour l’application.
Ajoutez une nouvelle classe ProductsContext.cs
au projet avec le code suivant :
using Microsoft.EntityFrameworkCore;
namespace GetStartedWinForms;
public class ProductsContext : 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");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasData(
new Category { CategoryId = 1, Name = "Cheese" },
new Category { CategoryId = 2, Name = "Meat" },
new Category { CategoryId = 3, Name = "Fish" },
new Category { CategoryId = 4, Name = "Bread" });
modelBuilder.Entity<Product>().HasData(
new Product { ProductId = 1, CategoryId = 1, Name = "Cheddar" },
new Product { ProductId = 2, CategoryId = 1, Name = "Brie" },
new Product { ProductId = 3, CategoryId = 1, Name = "Stilton" },
new Product { ProductId = 4, CategoryId = 1, Name = "Cheshire" },
new Product { ProductId = 5, CategoryId = 1, Name = "Swiss" },
new Product { ProductId = 6, CategoryId = 1, Name = "Gruyere" },
new Product { ProductId = 7, CategoryId = 1, Name = "Colby" },
new Product { ProductId = 8, CategoryId = 1, Name = "Mozzela" },
new Product { ProductId = 9, CategoryId = 1, Name = "Ricotta" },
new Product { ProductId = 10, CategoryId = 1, Name = "Parmesan" },
new Product { ProductId = 11, CategoryId = 2, Name = "Ham" },
new Product { ProductId = 12, CategoryId = 2, Name = "Beef" },
new Product { ProductId = 13, CategoryId = 2, Name = "Chicken" },
new Product { ProductId = 14, CategoryId = 2, Name = "Turkey" },
new Product { ProductId = 15, CategoryId = 2, Name = "Prosciutto" },
new Product { ProductId = 16, CategoryId = 2, Name = "Bacon" },
new Product { ProductId = 17, CategoryId = 2, Name = "Mutton" },
new Product { ProductId = 18, CategoryId = 2, Name = "Pastrami" },
new Product { ProductId = 19, CategoryId = 2, Name = "Hazlet" },
new Product { ProductId = 20, CategoryId = 2, Name = "Salami" },
new Product { ProductId = 21, CategoryId = 3, Name = "Salmon" },
new Product { ProductId = 22, CategoryId = 3, Name = "Tuna" },
new Product { ProductId = 23, CategoryId = 3, Name = "Mackerel" },
new Product { ProductId = 24, CategoryId = 4, Name = "Rye" },
new Product { ProductId = 25, CategoryId = 4, Name = "Wheat" },
new Product { ProductId = 26, CategoryId = 4, Name = "Brioche" },
new Product { ProductId = 27, CategoryId = 4, Name = "Naan" },
new Product { ProductId = 28, CategoryId = 4, Name = "Focaccia" },
new Product { ProductId = 29, CategoryId = 4, Name = "Malted" },
new Product { ProductId = 30, CategoryId = 4, Name = "Sourdough" },
new Product { ProductId = 31, CategoryId = 4, Name = "Corn" },
new Product { ProductId = 32, CategoryId = 4, Name = "White" },
new Product { ProductId = 33, CategoryId = 4, Name = "Soda" });
}
}
Veillez à générer la solution à ce stade.
Ajout de contrôles au formulaire
L’application affiche une liste de catégories et une liste de produits. Lorsqu'une catégorie est sélectionnée dans la première liste, la deuxième liste change pour afficher les produits de cette catégorie. Ces listes peuvent être modifiées pour ajouter, supprimer ou modifier des produits et des catégories, et ces modifications peuvent être enregistrées dans la base de données SQLite en cliquant sur un bouton Enregistrer.
Remplacez le nom du formulaire principal de
Form1
àMainForm
.Et remplacez le titre par « Produits et catégories ».
À l’aide de la boîte à outils, ajoutez deux contrôles
DataGridView
, organisés en regard des uns des autres.Dans les propriétés du premier
DataGridView
, remplacez le nom pardataGridViewCategories
.Dans les propriétés du deuxième
DataGridView
, remplacez le nom pardataGridViewProducts
.En outre, à l’aide de la boîte à outils, ajoutez un contrôle
Button
.Nommez le bouton
buttonSave
et donnez-lui le texte « Enregistrer ». Le formulaire doit ressembler à ceci :
Liaison de données
L’étape suivante consiste à connecter les types Product
et Category
du modèle aux contrôles DataGridView
. Les données chargées par EF Core sont ainsi liées aux contrôles, de sorte que les entités suivies par EF Core sont synchronisées avec celles affichées dans les contrôles.
Cliquez sur le Glyphe d’action du concepteur sur le premier
DataGridView
. Il s’agit du petit bouton situé en haut à droite du contrôle.Cette opération ouvre la liste d’actions à partir de laquelle la liste déroulante de la source de données Choisie est accessible. Nous n’avons pas encore créé de source de données, donc accédez au bas et choisissez Ajouter une nouvelle source de données d’objet....
Choisissez Catégorie pour créer une source de données d’objet pour les catégories, puis cliquez sur OK.
Conseil
Si aucun type de source de données n’apparaît ici, assurez-vous que
Product.cs
,Category.cs
etProductsContext.cs
n’ont jamais été ajoutés au projet et que la solution a été générée.Maintenant, la liste déroulante Choisir une source de données contient la source de données objet que nous venons de créer. Développez Autres sources de données, puis Sources de données du projet, puis choisissez Catégorie.
La deuxième
DataGridView
sera liée aux produits. Toutefois, au lieu de la liaison au type de niveau supérieurProduct
, elle sera liée à la navigationProducts
à partir de la liaisonCategory
du premierDataGridView
. Cela signifie que lorsqu'une catégorie est sélectionnée dans la première vue, les produits de cette catégorie seront automatiquement utilisés dans la seconde vue.À l’aide du Glyphe d’action du concepteur sur la deuxième
DataGridView
, choisissez Choisir une source de données, puis développez lecategoryBindingSource
et choisissezProducts
.
Configuration de ce qui s’affiche
Par défaut, une colonne est créée dans la DataGridView
de chaque propriété des types liés. De plus, les valeurs de chacune de ces propriétés peuvent être modifiées par l'utilisateur. Cependant, certaines valeurs, telles que les valeurs de la clé primaire, sont conceptuellement en lecture seule et ne doivent donc pas être modifiées. En outre, certaines propriétés, telles que la propriété de clé étrangère CategoryId
et la navigation Category
ne sont pas utiles à l’utilisateur, et doivent donc être masquées.
Conseil
Dans une application réelle, il est courant de masquer les propriétés des clés primaires. Ils sont laissés visibles ici pour permettre de voir facilement ce que fait EF Core en coulisses.
Cliquez avec le bouton droit sur le premier
DataGridView
et choisissez Modifier les colonnes....Définissez la colonne
CategoryId
, qui représente la clé primaire, en lecture seule, puis cliquez sur OK.Cliquez avec le bouton droit sur le deuxième
DataGridView
et choisissez Modifier les colonnes.... Définissez la colonneProductId
en lecture seule et supprimez les colonnesCategoryId
etCategory
, puis cliquez sur OK.
Connexion à EF Core
L'application a maintenant besoin d'une petite quantité de code pour connecter EF Core aux contrôles liés aux données.
Ouvrez le code
MainForm
en cliquant avec le bouton droit sur le fichier et en choisissant Afficher le code.Ajoutez un champ privé pour contenir la
DbContext
pour la session et ajoutez des remplacements pour les méthodes et les méthodesOnLoad
etOnClosing
. Le code doit se présenter comme ceci :
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
}
}
La méthode OnLoad
est appelée lorsque le formulaire est chargé. À ce stade
- Une instance du
ProductsContext
est créée et sera utilisée pour charger et suivre les modifications apportées aux produits et aux catégories affichés par l’application. EnsureCreated
est appelé surDbContext
pour créer la base de données SQLite si elle n’existe pas déjà. Il s'agit d'un moyen rapide de créer une base de données lors du prototypage ou du test d'applications. Toutefois, si le modèle change, la base de données devra être supprimée pour pouvoir être recréée. (La ligneEnsureDeleted
peut être non commentée pour supprimer et recréer facilement la base de données lors de l’exécution de l’application.) Vous pouvez plutôt utiliser EF Core Migrations pour modifier et mettre à jour le schéma de base de données sans perdre de données.EnsureCreated
remplira également la nouvelle base de données avec les données définies dans la méthodeProductsContext.OnModelCreating
.- La méthode d’extension
Load
est utilisée pour charger toutes les catégories de la base de données dans leDbContext
. Ces entités sont désormais suivies par leDbContext
, qui détecte les modifications apportées lorsque les catégories sont modifiées par l’utilisateur. - La propriété
categoryBindingSource.DataSource
est initialisée dans les catégories qui sont suivies par leDbContext
. Cela se fait en appelantLocal.ToBindingList()
sur la propriétéCategories
DbSet
.Local
fournit l’accès à une vue locale des catégories suivies, avec des événements connectés pour garantir que les données locales restent synchronisées avec les données affichées, et vice versa.ToBindingList()
expose ces données en tant queIBindingList
, qui est comprise par la liaison de données Windows Forms.
La méthode OnClosing
est appelée lorsque le formulaire est fermé. À ce stade, le fichier DbContext
est supprimé, ce qui garantit que toutes les ressources de base de données seront libérées et que le champ dbContext
est défini sur Null afin qu’il ne puisse pas être utilisé à nouveau.
Remplissage de la vue Produits
Si l'application est lancée à ce stade, elle devrait ressembler à ceci :
Notez que les catégories ont été chargées à partir de la base de données, mais que la table des produits reste vide. En outre, le bouton Enregistrer ne fonctionne pas.
Pour remplir la table des produits, EF Core doit charger les produits de la base de données pour la catégorie sélectionnée. Procédure à suivre :
Dans le concepteur du formulaire principal, sélectionnez les
DataGridView
pour les catégories.Dans les propriétés du
DataGridView
, choisissez les événements (bouton éclair), puis double-cliquez sur l’événement SelectionChanged.Cela créera un stub dans le code du formulaire principal pour qu'un événement soit déclenché chaque fois que la sélection de la catégorie change.
Renseignez le code de l’événement :
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
Dans ce code, s’il existe une session active DbContext
(non nulle), nous obtenons l’instance Category
liée à la ligne actuellement sélectionnée du DataViewGrid
. (Cela peut être null
si la ligne finale de la vue est sélectionnée, qui est utilisée pour créer de nouvelles catégories.) S’il existe une catégorie sélectionnée, il est demandé à DbContext
de charger les produits associés à cette catégorie. Pour ce faire :
- Obtention d’une
EntityEntry
pour l’instanceCategory
(dbContext.Entry(category)
) - Informer EF Core que nous voulons opérer sur la navigation de collection
Products
de ceCategory
(.Collection(e => e.Products)
) - Enfin, en disant à EF Core que nous voulons charger cette collection de produits à partir de la base de données (
.Load();
)
Conseil
Lorsque Load
est appelé, EF Core accède uniquement à la base de données pour charger les produits s’ils n’ont pas déjà été chargés.
Si l'application est à nouveau exécutée, elle devrait charger les produits appropriés chaque fois qu'une catégorie est sélectionnée :
Enregistrement des modifications
Enfin, le bouton Enregistrer peut être connecté à EF Core afin que toutes les modifications apportées aux produits et catégories soient enregistrées dans la base de données.
Dans le concepteur du formulaire principal, sélectionnez le bouton Enregistrer.
Dans les propriétés du
Button
, choisissez les événements (bouton éclair), puis double-cliquez sur l’événement Click.Renseignez le code de l’événement :
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
Ce code appelle SaveChanges
sur le DbContext
, qui enregistre les modifications apportées à la base de données SQLite. Si aucune modification n'a été apportée, il n'y a pas d'opération et aucun appel à la base de données n'est effectué. Après l’enregistrement, les contrôles DataGridView
sont actualisés. En effet, EF Core lit dans la base de données les valeurs de clé primaire générées pour tous les nouveaux produits et catégories. Appeler Refresh
met à jour l’affichage avec ces valeurs générées.
L’application finale
Voici le code complet du formulaire principal :
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
}
}
L'application peut maintenant être exécutée et les produits et catégories peuvent être ajoutés, supprimés et modifiés. Notez que si le bouton Enregistrer est cliqué avant de fermer l’application, toutes les modifications apportées seront stockées dans la base de données et rechargées lorsque l’application est redémarré. Si vous ne cliquez pas sur Enregistrer, les modifications sont perdues lorsque l’application est redémarrée.
Conseil
Une nouvelle catégorie ou un produit peut être ajouté à une DataViewControl
en utilisant la ligne vide en bas du contrôle. Une ligne peut être supprimée en la sélectionnant et en appuyant sur la touche Sup.
Avant l’enregistrement
Après l’enregistrement
Notez que les valeurs de clé primaire pour la catégorie ajoutée et les produits sont remplies lorsque vous cliquez sur Enregistrer.