Wprowadzenie do formularzy systemu Windows

W tym przewodniku krok po kroku pokazano, jak utworzyć prostą aplikację Windows Forms (WinForms) wspieraną przez bazę danych SQLite. Aplikacja używa programu Entity Framework Core (EF Core) do ładowania danych z bazy danych, śledzenia zmian wprowadzonych w tych danych i utrwalania tych zmian z powrotem w bazie danych.

Zrzuty ekranu i listy kodu w tym przewodniku pochodzą z programu Visual Studio 2022 17.3.0.

Napiwek

Przykład z tego artykułu można zobaczyć w witrynie GitHub.

Wymagania wstępne

Aby ukończyć ten przewodnik, musisz mieć zainstalowany program Visual Studio 2022 w wersji 17.3 lub nowszej z obciążeniem pulpitu platformy .NET. Aby uzyskać więcej informacji na temat instalowania najnowszej wersji programu Visual Studio, zobacz Instalowanie programu Visual Studio.

Tworzenie aplikacji

  1. Otwórz program Visual Studio.

  2. W oknie uruchamiania wybierz pozycję Utwórz nowy projekt.

  3. Wybierz pozycję Aplikacja Windows Forms, a następnie wybierz pozycję Dalej.

    Create a new Windows Forms project

  4. Na następnym ekranie nadaj projektowi nazwę, na przykład GetStartedWinForms, a następnie wybierz pozycję Dalej.

  5. Na następnym ekranie wybierz wersję platformy .NET do użycia. Ten przewodnik został utworzony za pomocą platformy .NET 7, ale powinien również pracować z nowszymi wersjami.

  6. Wybierz pozycję Utwórz.

Instalowanie pakietów NuGet platformy EF Core

  1. Kliknij rozwiązanie prawym przyciskiem myszy i wybierz polecenie Zarządzaj pakietami NuGet dla rozwiązania...

    Manage NuGet Packages for Solution

  2. Wybierz kartę Przeglądaj i wyszukaj ciąg "Microsoft.EntityFrameworkCore.Sqlite".

  3. Wybierz pakiet Microsoft.EntityFrameworkCore.Sqlite.

  4. Sprawdź projekt GetStartedWinForms w okienku po prawej stronie.

  5. Wybierz najnowszą wersję. Aby użyć wersji wstępnej, upewnij się, że pole Uwzględnij wersję wstępną jest zaznaczone.

  6. Kliknij pozycję Zainstaluj

    Install the Microsoft.EntityFrameworkCore.Sqlite package

Uwaga

Microsoft.EntityFrameworkCore.Sqlite to pakiet "dostawca bazy danych" do korzystania z programu EF Core z bazą danych SQLite. Podobne pakiety są dostępne dla innych systemów baz danych. Zainstalowanie pakietu dostawcy bazy danych automatycznie wprowadza wszystkie zależności potrzebne do korzystania z programu EF Core z tym systemem bazy danych. Obejmuje to pakiet podstawowy Microsoft.EntityFrameworkCore .

Definiowanie modelu

W tym przewodniku zaimplementujemy model przy użyciu polecenia "Code First". Oznacza to, że program EF Core utworzy tabele i schemat bazy danych na podstawie zdefiniowanych klas języka C#. Zobacz Zarządzanie schematami baz danych, aby zobaczyć, jak zamiast tego używać istniejącej bazy danych.

  1. Kliknij prawym przyciskiem myszy projekt i wybierz polecenie Dodaj, a następnie pozycję Klasa... , aby dodać nową klasę.

    Add new class

  2. Użyj nazwy pliku Product.cs i zastąp kod klasy:

    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. Powtórz polecenie , aby utworzyć Category.cs przy użyciu następującego kodu:

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

Właściwość Products klasy Category i Category właściwości w Product klasie są nazywane "nawigacjami". W programie EF Core nawigacje definiują relację między dwoma typami jednostek. W takim przypadku nawigacja Product.Category odwołuje się do kategorii, do której należy dany produkt. Podobnie nawigacja Category.Products po kolekcji zawiera wszystkie produkty dla danej kategorii.

Napiwek

W przypadku korzystania z formularzy systemu Windows element , ObservableCollectionListSourcektóry implementuje IListSourceelement , może służyć do nawigacji kolekcji. Nie jest to konieczne, ale poprawia środowisko dwukierunkowego powiązania danych.

Definiowanie elementu DbContext

W programie EF Core klasa pochodna DbContext jest używana do konfigurowania typów jednostek w modelu i działania jako sesji interakcji z bazą danych. W najprostszym przypadku DbContext klasa:

  • Zawiera DbSet właściwości dla każdego typu jednostki w modelu.
  • Zastępuje metodę w celu skonfigurowania OnConfiguring dostawcy bazy danych i parametry połączenia do użycia. Aby uzyskać więcej informacji, zobacz Konfigurowanie elementu DbContext .

W tym przypadku klasa DbContext zastępuje również metodę OnModelCreating w celu dostarczenia przykładowych danych dla aplikacji.

Dodaj nową ProductsContext.cs klasę do projektu przy użyciu następującego kodu:

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

Upewnij się, że w tym momencie skompiluj rozwiązanie .

Dodawanie kontrolek do formularza

Aplikacja wyświetli listę kategorii i listę produktów. Po wybraniu kategorii na pierwszej liście druga lista zmieni się, aby wyświetlić produkty dla tej kategorii. Te listy można modyfikować w celu dodawania, usuwania lub edytowania produktów i kategorii, a te zmiany można zapisać w bazie danych SQLite, klikając przycisk Zapisz .

  1. Zmień nazwę formularza głównego z Form1 na MainForm.

    Rename Form1 to MainForm

  2. Zmień tytuł na "Produkty i kategorie".

    Title MainForm as

  3. Za pomocą przybornika dodaj dwie DataGridView kontrolki ułożone obok siebie.

    Add DataGridView

  4. W właściwości dla pierwszego DataGridViewelementu zmień nazwę na dataGridViewCategories.

  5. W obszarze Właściwości dla drugiego DataGridViewelementu zmień nazwę na dataGridViewProducts.

  6. Ponadto za pomocą przybornika dodaj kontrolkęButton.

  7. Nadaj przyciskowi buttonSave nazwę i nadaj mu tekst "Zapisz". Formularz powinien wyglądać następująco:

    Form layout

Powiązanie danych

Następnym krokiem jest połączenie typów Product i Category z modelu z kontrolkami DataGridView . Spowoduje to powiązanie danych załadowanych przez program EF Core z kontrolkami, tak aby jednostki śledzone przez program EF Core były synchronizowane z tymi wyświetlanymi w kontrolkach.

  1. Kliknij Projektant akcji Glyph na pierwszym DataGridViewobiekcie . Jest to mały przycisk w prawym górnym rogu kontrolki.

    The Designer Action Glyph

  2. Spowoduje to otwarcie listy akcji, z której można uzyskać dostęp do listy rozwijanej Wybierz źródło danych. Nie utworzyliśmy jeszcze źródła danych, więc przejdź do dołu i wybierz pozycję Dodaj nowe źródło danych obiektu....

    Add new Object Data Source

  3. Wybierz pozycję Kategoria , aby utworzyć źródło danych obiektu dla kategorii, a następnie kliknij przycisk OK.

    Choose Category data source type

    Napiwek

    Jeśli w tym miejscu nie są wyświetlane żadne typy źródeł danych, upewnij się, że Product.cselement i ProductsContext.csCategory.cs zostały dodane do projektu, a rozwiązanie zostało skompilowane.

  4. Teraz lista rozwijana Wybierz źródło danych zawiera właśnie utworzone źródło danych obiektu. Rozwiń węzeł Inne źródła danych, a następnie pozycję Project Data Sources (Źródła danych projektu) i wybierz pozycję Category (Kategoria).

    Choose Category data source

    Drugi DataGridView będzie powiązany z produktami. Jednak zamiast wiązać się z typem najwyższego poziomu Product , zamiast tego zostanie on powiązany z nawigacją Products z Category powiązania pierwszego DataGridViewelementu . Oznacza to, że po wybraniu kategorii w pierwszym widoku produkty dla tej kategorii będą automatycznie używane w drugim widoku.

  5. Używając Projektant akcji Glyph w drugimDataGridView, wybierz pozycję Wybierz źródło danych, a następnie rozwiń categoryBindingSource i wybierz pozycję Products.

    Choose Products data source

Konfigurowanie wyświetlanych funkcji

Domyślnie kolumna jest tworzona w DataGridView obiekcie dla każdej właściwości powiązanych typów. Ponadto wartości dla każdej z tych właściwości można edytować przez użytkownika. Jednak niektóre wartości, takie jak wartości klucza podstawowego, są koncepcyjnie tylko do odczytu i nie powinny być edytowane. Ponadto niektóre właściwości, takie jak właściwość klucza obcego CategoryId i nawigacja Category , nie są przydatne dla użytkownika, a więc powinny być ukryte.

Napiwek

Często ukrywa się właściwości klucza podstawowego w rzeczywistej aplikacji. Są one widoczne tutaj, aby ułatwić sprawdzenie, co program EF Core robi za kulisami.

  1. Kliknij prawym przyciskiem myszy pierwszy DataGridView i wybierz polecenie Edytuj kolumny....

    Edit DataGridView columns

  2. Ustaw kolumnę CategoryId , która reprezentuje klucz podstawowy, tylko do odczytu, a następnie kliknij przycisk OK.

    Make CategoryId column read-only

  3. Kliknij prawym przyciskiem myszy drugą pozycję DataGridView i wybierz polecenie Edytuj kolumny.... Ustaw kolumnę ProductId jako tylko do odczytu, a następnie usuń CategoryId kolumny i Category , a następnie kliknij przycisk OK.

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

Połączenie do platformy EF Core

Aplikacja potrzebuje teraz niewielkiej ilości kodu, aby połączyć program EF Core z kontrolkami powiązanymi z danymi.

  1. MainForm Otwórz kod, klikając go prawym przyciskiem myszy i wybierając polecenie Wyświetl kod.

    View Code

  2. Dodaj pole prywatne do przechowywania DbContext dla sesji i dodaj przesłonięcia dla OnLoad metod i OnClosing . Kod powinien wyglądać następująco:

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

Metoda jest wywoływana OnLoad po załadowaniu formularza. W tej chwili

  • Zostanie utworzone wystąpienie ProductsContext klasy , które będzie używane do ładowania i śledzenia zmian w produktach i kategoriach wyświetlanych przez aplikację.
  • EnsureCreated Metoda jest wywoływana w elemecie DbContext , aby utworzyć bazę danych SQLite, jeśli jeszcze nie istnieje. Jest to szybki sposób tworzenia bazy danych podczas tworzenia prototypów lub testowania aplikacji. Jeśli jednak model ulegnie zmianie, należy usunąć bazę danych, aby można było ją utworzyć ponownie. (Wiersz EnsureDeleted może być nieoznakowany, aby łatwo usunąć i ponownie utworzyć bazę danych po uruchomieniu aplikacji). Zamiast tego możesz użyć migracji platformy EF Core do modyfikowania i aktualizowania schematu bazy danych bez utraty danych.
  • EnsureCreated spowoduje również wypełnienie nowej bazy danych danymi zdefiniowanymi w metodzie ProductsContext.OnModelCreating .
  • Metoda Load rozszerzenia służy do ładowania wszystkich kategorii z bazy danych do klasy DbContext. Te jednostki będą teraz śledzone przez element DbContext, który wykryje wszelkie zmiany wprowadzone podczas edycji kategorii przez użytkownika.
  • Właściwość categoryBindingSource.DataSource jest inicjowana do kategorii, które są śledzone przez element DbContext. Odbywa się to przez wywołanie Local.ToBindingList()CategoriesDbSet właściwości . Local zapewnia dostęp do lokalnego widoku śledzonych kategorii z zdarzeniami podłączonymi w celu zapewnienia, że dane lokalne pozostają zsynchronizowane z wyświetlanymi danymi i na odwrót. ToBindingList() Uwidacznia te dane jako IBindingListelement , który jest rozumiany przez powiązanie danych formularzy systemu Windows.

Metoda jest wywoływana OnClosing po zamknięciu formularza. W tej chwili obiekt DbContext zostanie usunięty, co gwarantuje, że wszystkie zasoby bazy danych zostaną zwolnione, a dbContext pole zostanie ustawione na wartość null, aby nie można było go ponownie użyć.

Wypełnianie widoku Produkty

Jeśli aplikacja została uruchomiona w tym momencie, powinna wyglądać mniej więcej tak:

Fist run of the application

Zwróć uwagę, że kategorie zostały załadowane z bazy danych, ale tabela products pozostaje pusta. Ponadto przycisk Zapisz nie działa.

Aby wypełnić tabelę products, program EF Core musi załadować produkty z bazy danych dla wybranej kategorii. Aby to osiągnąć:

  1. W projektancie formularza głównego wybierz dla DataGridView kategorii .

  2. W obszarze WłaściwościDataGridViewprogramu wybierz zdarzenia (przycisk błyskawica), a następnie kliknij dwukrotnie zdarzenie SelectionChanged .

    Add the SelectionChanged event

    Spowoduje to utworzenie wycinku w kodzie formularza głównego dla zdarzenia, które ma zostać wyzwolone za każdym razem, gdy wybór kategorii ulegnie zmianie.

  3. Wypełnij kod zdarzenia:

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

W tym kodzie, jeśli istnieje aktywna (niepusta) DbContext sesja, uzyskamy Category wystąpienie powiązane z aktualnie wybranym wierszem .DataViewGrid (Może to być null , jeśli wybrano ostatni wiersz w widoku, który jest używany do tworzenia nowych kategorii). Jeśli istnieje wybrana kategoria, DbContext zostanie ona poinstruowana o załadowaniu produktów skojarzonych z tą kategorią. Odbywa się to przez:

  • Pobieranie elementu EntityEntry dla Category wystąpienia (dbContext.Entry(category))
  • Poinformuj platformę Products EF Core o tym, że chcemy pracować na nawigacji Category po kolekcji (.Collection(e => e.Products))
  • Na koniec informujemy platformę EF Core, że chcemy załadować kolekcję produktów z bazy danych (.Load();)

Napiwek

Po Load wywołaniu program EF Core będzie uzyskiwać dostęp do bazy danych tylko w celu załadowania produktów, jeśli nie zostały jeszcze załadowane.

Jeśli aplikacja zostanie uruchomiona ponownie, należy załadować odpowiednie produkty za każdym razem, gdy wybrano kategorię:

Products are loaded

Zapisywanie zmian

Na koniec przycisk Zapisz można połączyć z platformą EF Core, aby wszelkie zmiany wprowadzone w produktach i kategoriach zostały zapisane w bazie danych.

  1. W projektancie formularza głównego wybierz przycisk Zapisz .

  2. W obszarze WłaściwościButtonwybierz zdarzenia (przycisk pioruna ), a następnie kliknij dwukrotnie zdarzenie Kliknij .

    Add the Click event for Save

  3. Wypełnij kod zdarzenia:

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

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

Ten kod wywołuje SaveChanges metodę DbContext, która zapisuje wszelkie zmiany wprowadzone w bazie danych SQLite. Jeśli nie wprowadzono żadnych zmian, jest to operacja bez operacji i nie zostanie wykonane żadne wywołanie bazy danych. Po zapisaniu kontrolki DataGridView zostaną odświeżone. Dzieje się tak, ponieważ program EF Core odczytuje wygenerowane wartości klucza podstawowego dla wszystkich nowych produktów i kategorii z bazy danych. Wywoływanie Refresh aktualizacji ekranu przy użyciu tych wygenerowanych wartości.

Ostateczna aplikacja

Oto pełny kod formularza głównego:

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

Teraz można uruchomić aplikację, a produkty i kategorie można dodawać, usuwać i edytować. Zwróć uwagę, że jeśli przycisk Zapisz zostanie kliknięty przed zamknięciem aplikacji, wszystkie wprowadzone zmiany zostaną zapisane w bazie danych i ponownie załadowane po ponownym uruchomieniu aplikacji. Jeśli nie klikniesz przycisku Zapisz , wszelkie zmiany zostaną utracone po ponownym uruchomieniu aplikacji.

Napiwek

Do pustego wiersza w dolnej części kontrolki można dodać nową kategorię DataViewControl lub produkt. Wiersz można usunąć, wybierając go i naciskając klawisz Del .

Przed zapisaniem

The running application before clicking Save

Po zapisaniu

The running application after clicking Save

Zwróć uwagę, że wartości klucza podstawowego dla dodanej kategorii i produktów są wypełniane po kliknięciu przycisku Zapisz .

Dowiedz się więcej