Komma igång med Windows Forms

Den här stegvisa genomgången visar hur du skapar ett enkelt Windows Forms-program (WinForms) som backas upp av en SQLite-databas. Programmet använder Entity Framework Core (EF Core) för att läsa in data från databasen, spåra ändringar som gjorts i dessa data och spara ändringarna i databasen igen.

Skärmbilderna och kodlistorna i den här genomgången tas från Visual Studio 2022 17.3.0.

Tips/Råd

Du kan visa den här artikelns exempel på GitHub.

Förutsättningar

Du måste ha Visual Studio 2022 17.3 eller senare installerat med .NET Desktop-arbetsbelastningen vald för att slutföra den här genomgången. Mer information om hur du installerar den senaste versionen av Visual Studio finns i Installera Visual Studio.

Skapa programmet

  1. Öppna Visual Studio

  2. I startfönstret väljer du Skapa nytt projekt.

  3. Välj Windows Forms App och välj sedan Nästa.

    Skapa ett nytt Windows Forms-projekt

  4. På nästa skärm ger du projektet ett namn, till exempel GetStartedWinForms, och väljer Nästa.

  5. På nästa skärm väljer du den .NET-version som ska användas. Den här genomgången skapades med .NET 7, men den bör också fungera med senare versioner.

  6. Välj Skapa.

Installera EF Core NuGet-paket

  1. Högerklicka på lösningen och välj Hantera NuGet-paket för lösning...

    Hantera NuGet-paket för lösning

  2. Välj fliken Bläddra och sök efter "Microsoft.EntityFrameworkCore.Sqlite".

  3. Välj paketet Microsoft.EntityFrameworkCore.Sqlite .

  4. Kontrollera projektet GetStartedWinForms i den högra rutan.

  5. Välj den senaste versionen. Om du vill använda en förhandsversion kontrollerar du att rutan Inkludera förhandsversion är markerad.

  6. Klicka på Installera

    Installera paketet Microsoft.EntityFrameworkCore.Sqlite

Anmärkning

Microsoft.EntityFrameworkCore.Sqlite är paketet "databasprovider" för användning av EF Core med en SQLite-databas. Liknande paket är tillgängliga för andra databassystem. När du installerar ett databasproviderpaket får du automatiskt in alla beroenden som krävs för att använda EF Core med det databassystemet. Detta inkluderar baspaketet Microsoft.EntityFrameworkCore .

Definiera en modell

I den här genomgången implementerar vi en modell med hjälp av "Code First". Det innebär att EF Core skapar databastabellerna och schemat baserat på de C#-klasser som du definierar. Se Hantera databasscheman för att se hur du använder en befintlig databas i stället.

  1. Högerklicka på projektet och välj Lägg till och sedan Klass... för att lägga till en ny klass.

    Lägg till ny klass

  2. Använd filnamnet Product.cs och ersätt koden för klassen med:

    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. Upprepa för att skapa Category.cs med följande kod:

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

Egenskapen Products för Category klassen och Category egenskapen i Product klassen kallas "navigering". I EF Core definierar navigering en relation mellan två entitetstyper. I det här fallet refererar navigeringen Product.Category till den kategori som en viss produkt tillhör. Category.Products På samma sätt innehåller samlingsnavigering alla produkter för en viss kategori.

Tips/Råd

När du använder Windows Forms kan ObservableCollectionListSource, som implementerar IListSource, användas för samlingars navigering. Detta är inte nödvändigt, men det förbättrar upplevelsen av dubbelriktad databindning.

Definiera DbContext

I EF Core används en klass som härleds från DbContext för att konfigurera entitetstyper i en modell och fungera som en session för att interagera med databasen. I det enklaste fallet är en DbContext klass:

  • Innehåller DbSet egenskaper för varje entitetstyp i modellen.
  • Åsidosätter OnConfiguring metoden för att konfigurera databasprovidern och anslutningssträngen som ska användas. Mer information finns i Konfigurera en DbContext .

I det här fallet åsidosätter OnModelCreating klassen DbContext även metoden för att tillhandahålla exempeldata för programmet.

Lägg till en ny ProductsContext.cs klass i projektet med följande kod:

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

Kom ihåg att skapa lösningen just nu.

Lägga till kontroller i formuläret

Programmet visar en lista över kategorier och en lista över produkter. När en kategori väljs i den första listan ändras den andra listan för att visa produkter för den kategorin. Dessa listor kan ändras för att lägga till, ta bort eller redigera produkter och kategorier, och dessa ändringar kan sparas i SQLite-databasen genom att klicka på knappen Spara .

  1. Ändra namnet på huvudformuläret från Form1 till MainForm.

    Byt namn på Form1 till MainForm

  2. Och ändra rubriken till "Produkter och kategorier".

    Rubrik MainForm som

  3. Med hjälp av verktygslådan lägger du till två DataGridView kontroller, ordnade bredvid varandra.

    Lägg till DataGridView

  4. I Egenskaper för den första DataGridViewändrar du Namnet till dataGridViewCategories.

  5. I Egenskaper för den andra DataGridViewändrar du Namnet till dataGridViewProducts.

  6. Lägg även till en kontroll med hjälp av Button.

  7. Namnge knappen buttonSave och ge den texten "Spara". Formuläret bör se ut ungefär så här:

    Formulärlayout

Datalänkning

Nästa steg är att ansluta typerna Product och Category från modellen till DataGridView kontrollerna. Detta binder data som läses in av EF Core till kontrollerna, så att de entiteter som spåras av EF Core hålls synkroniserade med dem som visas i kontrollerna.

  1. Klicka på Designerens åtgärdsikon på den första DataGridView. Det här är den lilla knappen i kontrollens övre högra hörn.

    Designeråtgärdssymbolen

  2. Då öppnas åtgärdslistan, från vilken listrutan för Välj datakälla kan nås. Vi har inte skapat någon datakälla än, så gå till botten och välj Lägg till ny objektdatakälla....

    Lägg till ny objektdatakälla

  3. Välj Kategori för att skapa en objektdatakälla för kategorier och klicka på OK.

    Välj Typ av kategoridatakälla

    Tips/Råd

    Om inga typer av datakällor visas här kontrollerar du att Product.cs, Category.cs och ProductsContext.cs har lagts till i projektet och att lösningen har skapats.

  4. Listrutan Välj datakälla innehåller nu den objektdatakälla som vi nyss skapade. Expandera Andra datakällor, sedan Projektdatakällor och välj Kategori.

    Välj Kategoridatakälla

    Den andra DataGridView kommer att bindas till produkter. Men istället för att binda till den övergripande nivån Product, kommer den att bindas till navigeringen Products, utifrån bindningen Category av den första DataGridView. Det innebär att när en kategori väljs i den första vyn används produkterna för den kategorin automatiskt i den andra vyn.

  5. Med hjälp av designeråtgärden glyph på den andra DataGridViewväljer du Välj datakälla och expanderar categoryBindingSource sedan och väljer Products.

    Välj datakälla för produkter

Konfigurera vad som visas

Som standard skapas en kolumn i DataGridView för varje egenskap för de bundna typerna. Dessutom kan värdena för var och en av dessa egenskaper redigeras av användaren. Vissa värden, till exempel de primära nyckelvärdena, är dock begreppsmässigt skrivskyddade och bör därför inte redigeras. Dessutom är vissa egenskaper, till exempel CategoryId egenskapen för sekundärnyckel och navigeringen Category inte användbara för användaren, och bör därför vara dolda.

Tips/Råd

Det är vanligt att dölja primärnyckelegenskaper i ett verkligt program. De lämnas synliga här för att göra det enkelt att se vad EF Core gör i bakgrunden.

  1. Högerklicka på den första DataGridView och välj Redigera kolumner....

    Redigera DataGridView-kolumner

  2. Gör kolumnen CategoryId , som representerar den primära nyckeln, skrivskyddad och klicka på OK.

    Gör CategoryId-kolumnen skrivskyddad

  3. Högerklicka på den andra DataGridView och välj Redigera kolumner.... ProductId Gör kolumnen skrivskyddad och ta bort kolumnerna CategoryId och Category och klicka sedan på OK.

    Gör ProductId-kolumnen skrivskyddad och ta bort Kolumnerna CategoryId och Category

Ansluta till EF Core

Programmet behöver nu en liten mängd kod för att ansluta EF Core till de databundna kontrollerna.

  1. MainForm Öppna koden genom att högerklicka på filen och välja Visa kod.

    Visa kod

  2. Lägg till ett privat fält för att lagra DbContext för sessionen, och lägg till överskrivningar för OnLoad och OnClosing metoderna. Koden bör se ut så här:

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

Metoden OnLoad anropas när formuläret läses in. Just nu

  • En instans av ProductsContext skapas som ska användas för att läsa in och spåra ändringar av produkter och kategorier som visas av programmet.
  • EnsureCreated anropas på DbContext för att skapa SQLite-databasen om den inte redan finns. Det här är ett snabbt sätt att skapa en databas vid prototyper eller testning av program. Men om modellen ändras måste databasen tas bort så att den kan skapas igen. (Raden EnsureDeleted kan inte kommenteras för att enkelt ta bort och återskapa databasen när programmet körs.) Du kanske i stället vill använda EF Core-migreringar för att ändra och uppdatera databasschemat utan att förlora några data.
  • EnsureCreated fyller också i den nya databasen med de data som definierats i ProductsContext.OnModelCreating metoden.
  • Tilläggsmetoden Load används för att läsa in alla kategorier från databasen till DbContext. Dessa entiteter spåras nu av DbContext, som identifierar eventuella ändringar som görs när kategorierna redigeras av användaren.
  • Egenskapen categoryBindingSource.DataSource initialiseras med de kategorier som spåras av DbContext. Detta görs genom att anropa Local.ToBindingList() egenskapen CategoriesDbSet . Local ger åtkomst till en lokal vy över de spårade kategorierna, med händelser kopplade för att säkerställa att lokala data förblir synkroniserade med de data som visas och vice versa. ToBindingList() exponerar dessa data som en IBindingList, vilket förstås av Databindningen i Windows Forms.

Metoden OnClosing anropas när formuläret stängs. För närvarande DbContext tas bort, vilket säkerställer att alla databasresurser frigörs, och dbContext fältet är inställt på null så att det inte kan användas igen.

Fylla Produkter-vyn

Om programmet startas nu bör det se ut ungefär så här:

Första körning av programmet

Observera att kategorierna har lästs in från databasen, men produkttabellen förblir tom. Knappen Spara fungerar inte heller.

För att fylla i produkttabellen måste EF Core läsa in produkter från databasen för den valda kategorin. För att åstadkomma detta:

  1. I designern för huvudformuläret väljer du kategorier med DataGridView.

  2. I Egenskaper för DataGridViewväljer du händelserna (blixtknappen) och dubbelklickar på händelsen SelectionChanged .

    Lägg till händelsen SelectionChanged

    Detta skapar stub i huvudformulärkoden för en händelse som ska utlöses när kategorivalet ändras.

  3. Fyll i koden för händelsen:

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

Om det finns en aktiv (icke-null) DbContext -session i den här koden hämtar vi den Category instans som är bunden till den markerade raden i DataViewGrid. (Detta kan vara null om den sista raden i vyn är markerad, som används för att skapa nya kategorier.) Om det finns en vald kategori DbContext instrueras du att läsa in de produkter som är associerade med den kategorin. Detta görs genom att:

  • Hämta en EntityEntry för instansen Category (dbContext.Entry(category))
  • Att låta EF Core veta att vi vill arbeta med kollektionsnavigeringen för Products i Category (.Collection(e => e.Products))
  • Och slutligen berätta för EF Core att vi vill läsa in den samlingen av produkter från databasen (.Load();)

Tips/Råd

När Load anropas kommer EF Core bara åt databasen för att läsa in produkterna om de inte redan har lästs in.

Om programmet nu körs igen bör det läsa in lämpliga produkter när en kategori har valts:

Produkter läses in

Spara ändringar

Slutligen kan knappen Spara anslutas till EF Core så att alla ändringar som görs i produkterna och kategorierna sparas i databasen.

  1. I designern för huvudformuläret väljer du knappen Spara .

  2. I Egenskaper för Buttonväljer du händelserna (blixtknappen) och dubbelklickar på händelsen Klicka .

    Lägg till klickhändelsen för Spara

  3. Fyll i koden för händelsen:

private void buttonSave_Click(object sender, EventArgs e)
{
    this.dbContext!.SaveChanges();

    this.dataGridViewCategories.Refresh();
    this.dataGridViewProducts.Refresh();
}

Den här koden anropar SaveChangesDbContext, som sparar alla ändringar som gjorts i SQLite-databasen. Om inga ändringar har gjorts är det här en no-opoch inget databasanrop görs. Efter att DataGridView har sparats, uppdateras kontrollerna. Det beror på att EF Core läser genererade primära nyckelvärden för alla nya produkter och kategorier från databasen. Anropet Refresh uppdaterar visningen med dessa genererade värden.

Den slutliga ansökan

Här är den fullständiga koden för huvudformuläret:

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

Programmet kan nu köras och produkter och kategorier kan läggas till, tas bort och redigeras. Observera att om knappen Spara klickas innan programmet stängs lagras alla ändringar som görs i databasen och läses in igen när programmet startas på nytt. Om Spara inte klickas går alla ändringar förlorade när programmet startas på nytt.

Tips/Råd

En ny kategori eller produkt kan läggas till i en DataViewControl med hjälp av den tomma raden längst ned i kontrollen. Du kan ta bort en rad genom att markera den och trycka på Del-tangenten .

Innan du sparar

Det program som körs innan du klickar på Spara

När du har sparat

Det program som körs när du har klickat på Spara

Observera att de primära nyckelvärdena för den tillagda kategorin och produkterna fylls i när Spara klickas.

Lära sig mer