Introduzione a Windows Forms
Questa procedura dettagliata illustra come creare una semplice applicazione Windows Form (WinForms) supportata da un database SQLite. L'applicazione usa Entity Framework Core (EF Core) per caricare i dati dal database, tenere traccia delle modifiche apportate a tali dati e rendere persistenti tali modifiche al database.
Le schermate e gli elenchi di codice in questa procedura dettagliata sono tratti da Visual Studio 2022 17.3.0.
Suggerimento
È possibile visualizzare l'esempio di questo articolo in GitHub.
Prerequisiti
Per completare questa procedura dettagliata, è necessario che Visual Studio 2022 17.3 o versione successiva sia installato con il carico di lavoro desktop .NET selezionato. Per altre informazioni sull'installazione della versione più recente di Visual Studio, vedere Installare Visual Studio.
Creare l'applicazione
Aprire Visual Studio.
Nella finestra iniziale scegliere Crea nuovo progetto.
Scegliere Windows Form'app e quindi scegliere Avanti.
Nella schermata successiva assegnare un nome al progetto, ad esempio GetStartedWinForms e scegliere Avanti.
Nella schermata successiva scegliere la versione di .NET da usare. Questa procedura dettagliata è stata creata con .NET 7, ma dovrebbe essere usata anche con le versioni successive.
Scegliere Create (Crea).
Installare i pacchetti NuGet di EF Core
Fare clic con il pulsante destro del mouse sulla soluzione e scegliere Gestisci pacchetti NuGet per la soluzione...
Scegliere la scheda Sfoglia e cercare "Microsoft.EntityFrameworkCore.Sqlite".
Selezionare il pacchetto Microsoft.EntityFrameworkCore.Sqlite .
Controllare il progetto GetStartedWinForms nel riquadro destro.
Scegliere la versione più recente. Per usare una versione non definitiva, assicurarsi che la casella Includi versione non definitiva sia selezionata.
Fai clic su Install (Installa).
Nota
Microsoft.EntityFrameworkCore.Sqlite è il pacchetto "provider di database" per l'uso di EF Core con un database SQLite. Sono disponibili pacchetti simili per altri sistemi di database. L'installazione di un pacchetto del provider di database comporta automaticamente tutte le dipendenze necessarie per l'uso di EF Core con tale sistema di database. Include il pacchetto di base Microsoft.EntityFrameworkCore .
Definire un modello
In questa procedura dettagliata verrà implementato un modello usando "Code First". Questo significa che EF Core creerà le tabelle di database e lo schema in base alle classi C# definite. Vedere Gestione degli schemi di database per informazioni su come usare invece un database esistente.
Fare clic con il pulsante destro del mouse sul progetto e scegliere Aggiungi, quindi Classe... per aggiungere una nuova classe.
Usare il nome file
Product.cs
e sostituire il codice per la classe con: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!; }
Ripetere la procedura per creare
Category.cs
con il codice seguente: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 Products
proprietà nella Category
classe e la Category
proprietà della Product
classe sono denominate "navigations". In EF Core gli spostamenti definiscono una relazione tra due tipi di entità. In questo caso, la Product.Category
struttura di spostamento fa riferimento alla categoria a cui appartiene un determinato prodotto. Analogamente, lo Category.Products
spostamento nella raccolta contiene tutti i prodotti per una determinata categoria.
Suggerimento
Quando si usa Windows Form, , ObservableCollectionListSource
che implementa , può essere usato per gli spostamenti IListSource
nella raccolta. Questa operazione non è necessaria, ma migliora l'esperienza di data binding bidirezionale.
Definire DbContext
In EF Core viene usata una classe derivata da DbContext
per configurare i tipi di entità in un modello e fungere da sessione per interagire con il database. Nel caso più semplice, una DbContext
classe:
- Contiene
DbSet
proprietà per ogni tipo di entità nel modello. - Esegue l'override del
OnConfiguring
metodo per configurare il provider di database e stringa di connessione da usare. Per altre informazioni, vedere Configurazione di un dbContext .
In questo caso, la classe DbContext esegue anche l'override del OnModelCreating
metodo per fornire alcuni dati di esempio per l'applicazione.
Aggiungere una nuova ProductsContext.cs
classe al progetto con il codice seguente:
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" });
}
}
Assicurarsi di compilare la soluzione a questo punto.
Aggiunta di controlli al form
L'applicazione visualizzerà un elenco di categorie e un elenco di prodotti. Quando viene selezionata una categoria nel primo elenco, il secondo elenco cambierà in modo da visualizzare i prodotti per tale categoria. Questi elenchi possono essere modificati per aggiungere, rimuovere o modificare prodotti e categorie e queste modifiche possono essere salvate nel database SQLite facendo clic su un pulsante Salva .
Modificare il nome del modulo principale da
Form1
aMainForm
.Modificare il titolo in "Prodotti e categorie".
Usando la casella degli strumenti, aggiungere due
DataGridView
controlli disposti l'uno accanto all'altro.In Proprietà per il primo
DataGridView
modificare il nome indataGridViewCategories
.In Proprietà per il secondo
DataGridView
modificare il nome indataGridViewProducts
.Inoltre, usando la casella degli strumenti, aggiungere un
Button
controllo .Denominare il pulsante
buttonSave
e assegnargli il testo "Salva". Il modulo dovrebbe avere un aspetto simile al seguente:
Data binding
Il passaggio successivo consiste nel connettere i Product
tipi e Category
dal modello ai DataGridView
controlli. In questo modo i dati caricati da EF Core verranno associati ai controlli, in modo che le entità rilevate da EF Core vengano mantenute sincronizzate con quelle visualizzate nei controlli.
Fare clic sul glifo azione della finestra di progettazione nel primo
DataGridView
oggetto . Questo è il piccolo pulsante nell'angolo superiore destro del controllo.Verrà aperto l'elenco azioni da cui è possibile accedere all'elenco a discesa Scelta origine dati. Non è ancora stata creata un'origine dati, quindi passare alla parte inferiore e scegliere Aggiungi nuova origine dati oggetto....
Scegliere Categoria per creare un'origine dati oggetto per le categorie e fare clic su OK.
Suggerimento
Se non vengono visualizzati tipi di origine dati, assicurarsi che
Product.cs
e siano stati aggiunti al progetto e che la soluzione sia stata compilata.ProductsContext.cs
Category.cs
L'elenco a discesa Scegli origine dati contiene ora l'origine dati dell'oggetto appena creata. Espandere Altre origini dati, quindi Proiettare origini dati e scegliere Categoria.
Il secondo
DataGridView
sarà associato ai prodotti. Tuttavia, anziché eseguire l'associazione al tipo di primo livelloProduct
, verrà invece associato allaProducts
navigazione dall'associazioneCategory
del primoDataGridView
oggetto . Ciò significa che quando una categoria viene selezionata nella prima visualizzazione, i prodotti per tale categoria verranno usati automaticamente nella seconda visualizzazione.Usando il glifo azione della finestra di progettazione nel secondo
DataGridView
, scegliere Scegli origine dati, quindi espanderecategoryBindingSource
e scegliereProducts
.
Configurazione di ciò che viene visualizzato
Per impostazione predefinita, viene creata una colonna in DataGridView
per ogni proprietà dei tipi associati. Inoltre, i valori per ognuna di queste proprietà possono essere modificati dall'utente. Tuttavia, alcuni valori, ad esempio i valori della chiave primaria, sono concettualmente di sola lettura e quindi non devono essere modificati. Inoltre, alcune proprietà, ad esempio la CategoryId
proprietà di chiave esterna e lo Category
spostamento non sono utili per l'utente e quindi devono essere nascoste.
Suggerimento
È comune nascondere le proprietà della chiave primaria in un'applicazione reale. Vengono lasciati visibili qui per facilitare la visualizzazione di EF Core in background.
Fare clic con il pulsante destro del mouse sul primo
DataGridView
e scegliere Modifica colonne...Impostare la
CategoryId
colonna , che rappresenta la chiave primaria, di sola lettura e fare clic su OK.Fare clic con il pulsante destro del mouse sul secondo
DataGridView
e scegliere Modifica colonne... Impostare laProductId
colonna di sola lettura e rimuovere leCategoryId
colonne eCategory
quindi fare clic su OK.
Connessione a EF Core
L'applicazione richiede ora una piccola quantità di codice per connettere EF Core ai controlli associati a dati.
Aprire il
MainForm
codice facendo clic con il pulsante destro del mouse sul file e scegliendo Visualizza codice.Aggiungere un campo privato per contenere l'oggetto
DbContext
per la sessione e aggiungere sostituzioni per iOnLoad
metodi eOnClosing
. Il codice dovrebbe essere simile al seguente:
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;
}
}
}
Il OnLoad
metodo viene chiamato quando viene caricato il modulo. In questo momento
- Viene creata un'istanza
ProductsContext
di che verrà usata per caricare e tenere traccia delle modifiche apportate ai prodotti e alle categorie visualizzate dall'applicazione. EnsureCreated
viene chiamato suDbContext
per creare il database SQLite, se non esiste già. Si tratta di un modo rapido per creare un database durante la creazione di prototipi o il test delle applicazioni. Tuttavia, se il modello viene modificato, il database dovrà essere eliminato in modo che possa essere creato di nuovo. LaEnsureDeleted
riga può essere annullata come commento per eliminare e ricreare facilmente il database quando viene eseguita l'applicazione. È invece possibile usare le migrazioni di EF Core per modificare e aggiornare lo schema del database senza perdere dati.EnsureCreated
popola inoltre il nuovo database con i dati definiti nelProductsContext.OnModelCreating
metodo .- Il
Load
metodo di estensione viene usato per caricare tutte le categorie dal database inDbContext
. Queste entità verranno ora rilevate daDbContext
, che rileverà le modifiche apportate quando le categorie vengono modificate dall'utente. - La
categoryBindingSource.DataSource
proprietà viene inizializzata nelle categorie rilevate dall'oggettoDbContext
. A tale scopo, chiamareLocal.ToBindingList()
laCategories
DbSet
proprietà .Local
consente l'accesso a una visualizzazione locale delle categorie rilevate, con gli eventi associati per garantire che i dati locali rimangano sincronizzati con i dati visualizzati e viceversa.ToBindingList()
espone questi dati comeIBindingList
, che vengono riconosciuti da Windows Form data binding.
Il OnClosing
metodo viene chiamato quando la maschera viene chiusa. Al momento, l'oggetto DbContext
viene eliminato, che garantisce che tutte le risorse del database vengano liberate e che il dbContext
campo sia impostato su Null in modo che non possa essere usato di nuovo.
Popolamento della visualizzazione Prodotti
Se l'applicazione viene avviata a questo punto, dovrebbe essere simile alla seguente:
Si noti che le categorie sono state caricate dal database, ma la tabella products rimane vuota. Inoltre, il pulsante Salva non funziona.
Per popolare la tabella dei prodotti, EF Core deve caricare i prodotti dal database per la categoria selezionata. Per ottenere questo risultato:
Nella finestra di progettazione per il modulo principale selezionare per le
DataGridView
categorie.In Proprietà per
DataGridView
scegliere gli eventi (pulsante di fulmine) e fare doppio clic sull'evento SelectionChanged.Verrà creato lo stub nel codice modulo principale per generare un evento ogni volta che cambia la selezione della categoria.
Compilare il codice per l'evento:
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();
}
}
}
In questo codice, se è presente una sessione attiva (non Null), DbContext
si ottiene l'istanza Category
associata alla riga attualmente selezionata di DataViewGrid
. Può trattarsi null
di se è selezionata la riga finale nella visualizzazione, che viene usata per creare nuove categorie. Se è presente una categoria selezionata, viene DbContext
richiesto di caricare i prodotti associati a tale categoria. A tale scopo, è necessario:
- Recupero di un oggetto
EntityEntry
per l'istanzaCategory
(dbContext.Entry(category)
) - Comunicare a EF Core che si vuole operare sulla navigazione nella
Products
raccolta di taleCategory
elemento (.Collection(e => e.Products)
) - Infine, indicare a EF Core che si vuole caricare la raccolta di prodotti dal database (
.Load();
)
Suggerimento
Quando Load
viene chiamato, EF Core accederà al database solo per caricare i prodotti se non sono già stati caricati.
Se l'applicazione viene ora eseguita nuovamente, deve caricare i prodotti appropriati ogni volta che viene selezionata una categoria:
Salvataggio delle modifiche
Infine, il pulsante Salva può essere connesso a EF Core in modo che tutte le modifiche apportate ai prodotti e alle categorie vengano salvate nel database.
Nella finestra di progettazione del modulo principale selezionare il pulsante Salva .
In Proprietà per
Button
scegliere gli eventi (il pulsante fulmine) e fare doppio clic sull'evento Click.Compilare il codice per l'evento:
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
Questo codice chiama SaveChanges
in DbContext
, che salva tutte le modifiche apportate al database SQLite. Se non sono state apportate modifiche, si tratta di una chiamata no-op e non viene effettuata alcuna chiamata al database. Dopo il salvataggio, i DataGridView
controlli vengono aggiornati. Questo perché EF Core legge i valori di chiave primaria generati per tutti i nuovi prodotti e categorie del database. La chiamata Refresh
aggiorna la visualizzazione con questi valori generati.
Applicazione finale
Di seguito è riportato il codice completo per il modulo principale:
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'applicazione può ora essere eseguita e i prodotti e le categorie possono essere aggiunti, eliminati e modificati. Si noti che se si fa clic sul pulsante Salva prima di chiudere l'applicazione, eventuali modifiche apportate verranno archiviate nel database e ricaricate quando l'applicazione viene nuovamente avviata. Se non si fa clic su Salva , le modifiche andranno perse quando l'applicazione viene avviata di nuovo.
Suggerimento
È possibile aggiungere una nuova categoria o un prodotto a un DataViewControl
oggetto utilizzando la riga vuota nella parte inferiore del controllo. È possibile eliminare una riga selezionandola e premendo il tasto Canc .
Prima di salvare
Dopo il salvataggio
Si noti che i valori di chiave primaria per la categoria e i prodotti aggiunti vengono popolati quando si fa clic su Salva .