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

  1. Aprire Visual Studio.

  2. Nella finestra iniziale scegliere Crea nuovo progetto.

  3. Scegliere Windows Form'app e quindi scegliere Avanti.

    Create a new Windows Forms project

  4. Nella schermata successiva assegnare un nome al progetto, ad esempio GetStartedWinForms e scegliere Avanti.

  5. 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.

  6. Scegliere Crea.

Installare i pacchetti NuGet di EF Core

  1. Fare clic con il pulsante destro del mouse sulla soluzione e scegliere Gestisci pacchetti NuGet per la soluzione...

    Manage NuGet Packages for Solution

  2. Scegliere la scheda Sfoglia e cercare "Microsoft.EntityFrameworkCore.Sqlite".

  3. Selezionare il pacchetto Microsoft.EntityFrameworkCore.Sqlite .

  4. Controllare il progetto GetStartedWinForms nel riquadro destro.

  5. Scegliere la versione più recente. Per usare una versione non definitiva, assicurarsi che la casella Includi versione non definitiva sia selezionata.

  6. Fai clic su Install (Installa).

    Install the Microsoft.EntityFrameworkCore.Sqlite package

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.

  1. Fare clic con il pulsante destro del mouse sul progetto e scegliere Aggiungi, quindi Classe... per aggiungere una nuova classe.

    Add new class

  2. 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!;
    }
    
  3. 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, , ObservableCollectionListSourceche implementa , può essere usato per gli spostamenti IListSourcenella 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 .

  1. Modificare il nome del modulo principale da Form1 a MainForm.

    Rename Form1 to MainForm

  2. Modificare il titolo in "Prodotti e categorie".

    Title MainForm as

  3. Usando la casella degli strumenti, aggiungere due DataGridView controlli disposti l'uno accanto all'altro.

    Add DataGridView

  4. In Proprietà per il primo DataGridViewmodificare il nome in dataGridViewCategories.

  5. In Proprietà per il secondo DataGridViewmodificare il nome in dataGridViewProducts.

  6. Inoltre, usando la casella degli strumenti, aggiungere un Button controllo .

  7. Denominare il pulsante buttonSave e assegnargli il testo "Salva". Il modulo dovrebbe avere un aspetto simile al seguente:

    Form layout

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.

  1. Fare clic sul glifo azione della finestra di progettazione nel primo DataGridViewoggetto . Questo è il piccolo pulsante nell'angolo superiore destro del controllo.

    The Designer Action Glyph

  2. 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....

    Add new Object Data Source

  3. Scegliere Categoria per creare un'origine dati oggetto per le categorie e fare clic su OK.

    Choose Category data source type

    Suggerimento

    Se non vengono visualizzati tipi di origine dati, assicurarsi che Product.cse siano stati aggiunti al progetto e che la soluzione sia stata compilata.ProductsContext.csCategory.cs

  4. 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.

    Choose Category data source

    Il secondo DataGridView sarà associato ai prodotti. Tuttavia, anziché eseguire l'associazione al tipo di primo livello Product , verrà invece associato alla Products navigazione dall'associazione Category del primo DataGridViewoggetto . Ciò significa che quando una categoria viene selezionata nella prima visualizzazione, i prodotti per tale categoria verranno usati automaticamente nella seconda visualizzazione.

  5. Usando il glifo azione della finestra di progettazione nel secondo DataGridView, scegliere Scegli origine dati, quindi espandere categoryBindingSource e scegliere Products .

    Choose Products data source

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.

  1. Fare clic con il pulsante destro del mouse sul primo DataGridView e scegliere Modifica colonne...

    Edit DataGridView columns

  2. Impostare la CategoryId colonna , che rappresenta la chiave primaria, di sola lettura e fare clic su OK.

    Make CategoryId column read-only

  3. Fare clic con il pulsante destro del mouse sul secondo DataGridView e scegliere Modifica colonne... Impostare la ProductId colonna di sola lettura e rimuovere le CategoryId colonne e Category quindi fare clic su OK.

    Make ProductId column read-only and remove CategoryId and Category columns

Connessione ing in EF Core

L'applicazione richiede ora una piccola quantità di codice per connettere EF Core ai controlli associati a dati.

  1. Aprire il MainForm codice facendo clic con il pulsante destro del mouse sul file e scegliendo Visualizza codice.

    View Code

  2. Aggiungere un campo privato per contenere l'oggetto DbContext per la sessione e aggiungere sostituzioni per i OnLoad metodi e OnClosing . 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 su DbContext 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. La EnsureDeleted 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 nel ProductsContext.OnModelCreating metodo .
  • Il Load metodo di estensione viene usato per caricare tutte le categorie dal database in DbContext. Queste entità verranno ora rilevate da DbContext, che rileverà le modifiche apportate quando le categorie vengono modificate dall'utente.
  • La categoryBindingSource.DataSource proprietà viene inizializzata nelle categorie rilevate dall'oggetto DbContext. A tale scopo, chiamare Local.ToBindingList() la CategoriesDbSet 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 come IBindingList, 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:

Fist run of the application

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:

  1. Nella finestra di progettazione per il modulo principale selezionare per le DataGridView categorie.

  2. In Proprietà per DataGridViewscegliere gli eventi (pulsante di fulmine) e fare doppio clic sull'evento SelectionChanged.

    Add the SelectionChanged event

    Verrà creato lo stub nel codice modulo principale per generare un evento ogni volta che cambia la selezione della categoria.

  3. 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'istanza Category (dbContext.Entry(category))
  • Comunicare a EF Core che si vuole operare sulla navigazione nella Products raccolta di tale Category 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:

Products are loaded

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.

  1. Nella finestra di progettazione del modulo principale selezionare il pulsante Salva .

  2. In Proprietà per Buttonscegliere gli eventi (il pulsante fulmine) e fare doppio clic sull'evento Click.

    Add the Click event for Save

  3. 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

The running application before clicking Save

Dopo il salvataggio

The running application after clicking Save

Si noti che i valori di chiave primaria per la categoria e i prodotti aggiunti vengono popolati quando si fa clic su Salva .

Altre informazioni