Udostępnij za pośrednictwem


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.

Wskazówka

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.

    Utwórz nowy projekt Windows Forms

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

    Zarządzanie pakietami NuGet dla rozwiązania

  2. Wybierz kartę Przeglądaj i wyszukaj frazę "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

    Instalowanie pakietu Microsoft.EntityFrameworkCore.Sqlite

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. To obejmuje pakiet podstawowy Microsoft.EntityFrameworkCore.

Definiowanie modelu

W tym tutorialu zaimplementujemy model przy użyciu podejścia "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ę.

    Dodaj nową klasę

  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, 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 oraz właściwość Category klasy Product nazywa się "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.

Wskazówka

Podczas korzystania z Windows Forms, ObservableCollectionListSource, który implementuje IListSource, może być używany do nawigacji w ramach kolekcji. Nie jest to konieczne, ale poprawia doświadczenie związane z dwukierunkowym powiązaniem 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 klasa DbContext:

  • Zawiera DbSet właściwości dla każdego typu jednostki w modelu.
  • Nadpisuje metodę, aby skonfigurować dostawcę bazy danych OnConfiguring i łańcuch połączenia, które mają być używane. 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.

    Zmienianie nazwy formularza Form1 na MainForm

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

    Tytuł MainForm jako

  3. Używając przybornika , dodaj dwie kontrolki DataGridView, ułożone obok siebie.

    Dodaj 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. Korzystając także z Toolbox, dodaj kontrolkęButton.

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

    Układ formularza

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 Glyph akcji Projektanta na pierwszym DataGridView. Jest to mały przycisk w prawym górnym rogu kontrolki.

    Ikona akcji projektanta

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

    Dodawanie nowego źródła danych obiektu

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

    Wybierz typ źródła danych Kategorii

    Wskazówka

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

  4. Teraz lista Wybierz źródło danych rozwijana zawiera właśnie utworzone źródło danych obiektu. Rozwiń Inne źródła danych, a następnie Źródła danych projektu i wybierz Kategorię.

    Wybieranie źródła danych kategorii

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

  5. Korzystając z Symbolu akcji projektanta na drugim DataGridView, wybierz Wybierz źródło danych, a następnie rozwiń categoryBindingSource i wybierz Products.

    Wybieranie źródła danych produktów

Konfigurowanie wyświetlanych funkcji

Domyślnie w obiekcie DataGridView tworzona jest kolumna dla każdej właściwości typów powiązanych. 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.

Wskazówka

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

    Edytowanie kolumn DataGridView

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

    Ustaw kolumnę CategoryId jako tylko do odczytu

  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.

    Utwórz kolumnę ProductId tylko do odczytu i usuń kolumny CategoryId i Category

Nawiązywanie połączenia z platformą 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.

    Wyświetl kod

  2. Dodaj pole prywatne do przechowywania DbContext dla sesji oraz dodaj przesłonięcia dla metod OnLoad 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 OnLoad jest wywoływana podczas ładowania formularza. W tej chwili

  • Zostanie utworzone wystąpienie klasy ProductsContext, które będzie używane do ładowania i śledzenia zmian w produktach i kategoriach wyświetlanych przez aplikację.
  • EnsureCreated jest wywoływane w elemencie 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ć odkomentowany, aby łatwo usunąć i ponownie utworzyć bazę danych przy uruchamianiu aplikacji). Zamiast tego możesz użyć EF Core Migrations 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 rozszerzenia Load służy do ładowania wszystkich kategorii z bazy danych do 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() na właściwości CategoriesDbSet. 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 element IBindingList, który jest rozumiany przez powiązanie danych formularzy systemu Windows.

Metoda OnClosing jest wywoływana, gdy formularz zostanie zamknięty. 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 produktami

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

Pierwsze uruchomienie aplikacji

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 DataGridView dla kategorii.

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

    Dodaj zdarzenie SelectionChanged

    Spowoduje to utworzenie szablonu 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, to uzyskamy Category instancję powiązaną 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 wystąpienia Category (dbContext.Entry(category))
  • Poinformowanie EF Core, że chcemy operować nawigacją kolekcji Products tego Category (.Collection(e => e.Products))
  • Na koniec przekazujemy EF Core, że chcemy załadować tę kolekcję produktów z bazy danych (.Load();)

Wskazówka

Po wywołaniu Load, EF Core uzyska dostęp do bazy danych tylko po to, aby załadować produkty, jeśli jeszcze nie zostały załadowane.

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

Produkty są ładowane

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ściButton wybierz zdarzenia (przycisk pioruna), a następnie dwukrotnie kliknij zdarzenie Click.

    Dodaj zdarzenie kliknięcia dla przycisku Zapisz

  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 na DbContext, co zapisuje wszelkie zmiany wprowadzone w bazie danych SQLite. Jeśli nie wprowadzono żadnych zmian, jest to operacja bez efektu i nie zostanie wykonane żadne wywołanie do 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łanie Refresh aktualizuje ekran 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.

Wskazówka

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

Przed zapisaniem

Uruchomiona aplikacja przed kliknięciem przycisku Zapisz

Po zapisaniu

Uruchomiona aplikacja po kliknięciu przycisku Zapisz

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