Memulai Formulir Windows

Panduan langkah demi langkah ini menunjukkan cara membangun aplikasi Formulir Windows sederhana (WinForms) yang didukung oleh database SQLite. Aplikasi ini menggunakan Entity Framework Core (EF Core) untuk memuat data dari database, melacak perubahan yang dilakukan pada data tersebut, dan mempertahankan perubahan tersebut kembali ke database.

Cuplikan layar dan daftar kode dalam panduan ini diambil dari Visual Studio 2022 17.3.0.

Tip

Anda dapat melihat contoh artikel ini di GitHub.

Prasyarat

Anda harus menginstal Visual Studio 2022 17.3 atau yang lebih baru dengan beban kerja desktop .NET yang dipilih untuk menyelesaikan panduan ini. Untuk informasi selengkapnya tentang menginstal Visual Studio versi terbaru, lihat Menginstal Visual Studio.

Membuat Aplikasi

  1. Membuka Visual Studio

  2. Di jendela mulai, pilih Buat proyek baru.

  3. Pilih aplikasi Formulir Windows lalu pilih Berikutnya.

    Create a new Windows Forms project

  4. Di layar berikutnya, beri nama proyek, misalnya, GetStartedWinForms, dan pilih Berikutnya.

  5. Di layar berikutnya, pilih versi .NET yang akan digunakan. Panduan ini dibuat dengan .NET 7, tetapi juga harus berfungsi dengan versi yang lebih baru.

  6. Pilih Buat.

Menginstal paket EF Core NuGet

  1. Klik kanan pada solusi dan pilih Kelola Paket NuGet untuk Solusi...

    Manage NuGet Packages for Solution

  2. Pilih tab Telusuri dan cari "Microsoft.EntityFrameworkCore.Sqlite".

  3. Pilih paket Microsoft.EntityFrameworkCore.Sqlite .

  4. Periksa proyek GetStartedWinForms di panel kanan.

  5. Pilih versi terbaru. Untuk menggunakan versi pra-rilis, pastikan kotak Sertakan prarilis dicentang.

  6. Klik Instal

    Install the Microsoft.EntityFrameworkCore.Sqlite package

Catatan

Microsoft.EntityFrameworkCore.Sqlite adalah paket "penyedia database" untuk menggunakan EF Core dengan database SQLite. Paket serupa tersedia untuk sistem database lainnya. Menginstal paket penyedia database secara otomatis membawa semua dependensi yang diperlukan untuk menggunakan EF Core dengan sistem database tersebut. Ini termasuk paket dasar Microsoft.EntityFrameworkCore .

Tentukan Model

Dalam panduan ini kita akan menerapkan model menggunakan "Kode Pertama". Ini berarti bahwa EF Core akan membuat tabel database dan skema berdasarkan kelas C# yang Anda tentukan. Lihat Mengelola Skema Database untuk melihat cara menggunakan database yang sudah ada sebagai gantinya.

  1. Klik kanan pada proyek dan pilih Tambahkan, lalu Kelas... untuk menambahkan kelas baru.

    Add new class

  2. Gunakan nama Product.cs file dan ganti kode untuk kelas dengan:

    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. Ulangi untuk membuat Category.cs dengan kode berikut:

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

Properti Products pada Category kelas dan Category properti di Product kelas disebut "navigasi". Di EF Core, navigasi menentukan hubungan antara dua jenis entitas. Dalam hal ini, Product.Category navigasi mereferensikan kategori tempat produk tertentu berada. Demikian juga, Category.Products navigasi koleksi berisi semua produk untuk kategori tertentu.

Tip

Saat menggunakan Formulir Windows, ObservableCollectionListSource, yang mengimplementasikan IListSource, dapat digunakan untuk navigasi koleksi. Ini tidak diperlukan, tetapi meningkatkan pengalaman pengikatan data dua arah.

Tentukan DbContext

Di EF Core, kelas yang berasal dari DbContext digunakan untuk mengonfigurasi jenis entitas dalam model dan bertindak sebagai sesi untuk berinteraksi dengan database. Dalam kasus paling sederhana, kelas DbContext :

  • Berisi DbSet properti untuk setiap jenis entitas dalam model.
  • Mengambil alih OnConfiguring metode untuk mengonfigurasi penyedia database dan string koneksi untuk digunakan. Lihat Mengonfigurasi DbContext untuk informasi selengkapnya.

Dalam hal ini, kelas DbContext juga mengambil alih OnModelCreating metode untuk menyediakan beberapa data sampel untuk aplikasi.

Tambahkan kelas baru ProductsContext.cs ke proyek dengan kode berikut:

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

Pastikan untuk membangun solusi pada saat ini.

Menambahkan kontrol ke formulir

Aplikasi ini akan menampilkan daftar kategori dan daftar produk. Saat kategori dipilih dalam daftar pertama, maka daftar kedua akan berubah untuk menampilkan produk untuk kategori tersebut. Daftar ini dapat dimodifikasi untuk menambahkan, menghapus, atau mengedit produk dan kategori, dan perubahan ini dapat disimpan ke database SQLite dengan mengklik tombol Simpan .

  1. Ubah nama formulir utama dari Form1 menjadi MainForm.

    Rename Form1 to MainForm

  2. Dan ubah judul menjadi "Produk dan Kategori".

    Title MainForm as

  3. Menggunakan Kotak Alat, tambahkan dua DataGridView kontrol, disusun di samping satu sama lain.

    Add DataGridView

  4. Di Properti untuk yang pertamaDataGridView, ubah Nama menjadi dataGridViewCategories.

  5. Di Properti untuk yang kedua DataGridView, ubah Nama menjadi dataGridViewProducts.

  6. Juga menggunakan Kotak Alat, tambahkan Button kontrol.

  7. Beri nama tombol buttonSave dan berikan teks "Simpan". Formulir akan terlihat seperti ini:

    Form layout

Pengikatan data

Langkah selanjutnya adalah menyambungkan Product jenis dan Category dari model ke DataGridView kontrol. Ini akan mengikat data yang dimuat oleh EF Core ke kontrol, sehingga entitas yang dilacak oleh EF Core tetap sinkron dengan yang ditampilkan dalam kontrol.

  1. Klik Glyph Tindakan Perancang pada yang pertamaDataGridView. Ini adalah tombol kecil di sudut kanan atas kontrol.

    The Designer Action Glyph

  2. Ini membuka Daftar Tindakan, tempat drop-down untuk Memilih Sumber Data dapat diakses. Kami belum membuat sumber data, jadi buka bagian bawah dan pilih Tambahkan Sumber Data Objek baru....

    Add new Object Data Source

  3. Pilih Kategori untuk membuat sumber data objek untuk kategori, dan klik OK.

    Choose Category data source type

    Tip

    Jika tidak ada jenis sumber data yang muncul di sini, pastikan bahwa , dan telah ditambahkan ke proyek dan solusi telah dibuat.ProductsContext.csCategory.csProduct.cs

  4. Sekarang drop-down Pilih Sumber Data berisi sumber data objek yang baru saja kita buat. Perluas Sumber Data Lain, lalu Sumber Data Proyek, dan pilih Kategori.

    Choose Category data source

    Yang kedua DataGridView akan terikat pada produk. Namun, daripada mengikat ke jenis tingkat Product atas, sebaliknya akan terikat ke Products navigasi dari Category pengikatan yang pertama DataGridView. Ini berarti bahwa ketika kategori dipilih dalam tampilan pertama, produk untuk kategori tersebut akan secara otomatis digunakan dalam tampilan kedua.

  5. Menggunakan Glyph Tindakan Perancang pada yang keduaDataGridView, pilih Pilih Sumber Data, lalu perluas categoryBindingSource dan pilih Products.

    Choose Products data source

Mengonfigurasi apa yang ditampilkan

Secara default, kolom dibuat di DataGridView untuk setiap properti dari jenis terikat. Selain itu, nilai untuk setiap properti ini dapat diedit oleh pengguna. Namun, beberapa nilai, seperti nilai kunci utama, secara konseptual baca-saja, sehingga tidak boleh diedit. Selain itu CategoryId , beberapa properti, seperti properti kunci asing dan Category navigasi tidak berguna bagi pengguna, sehingga harus disembunyikan.

Tip

Adalah umum untuk menyembunyikan properti kunci utama dalam aplikasi nyata. Mereka dibiarkan terlihat di sini untuk memudahkan untuk melihat apa yang dilakukan EF Core di belakang layar.

  1. Klik kanan pada kolom pertama DataGridView dan pilih Edit Kolom....

    Edit DataGridView columns

  2. CategoryId Buat kolom, yang mewakili kunci utama, baca-saja, dan klik OK.

    Make CategoryId column read-only

  3. Klik kanan pada detik DataGridView dan pilih Edit Kolom.... ProductId Buat kolom baca-saja, dan hapus CategoryId kolom dan Category , lalu klik OK.

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

Koneksi ke EF Core

Aplikasi sekarang membutuhkan sejumlah kecil kode untuk menghubungkan EF Core ke kontrol terikat data.

  1. MainForm Buka kode dengan mengklik kanan pada file dan memilih Tampilkan Kode.

    View Code

  2. Tambahkan bidang privat untuk menahan DbContext sesi, dan tambahkan penimpaan OnLoad untuk metode dan OnClosing . Kode akan terlihat seperti ini:

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

Metode OnLoad ini dipanggil ketika formulir dimuat. Saat ini

  • Instans ProductsContext dibuat yang akan digunakan untuk memuat dan melacak perubahan pada produk dan kategori yang ditampilkan oleh aplikasi.
  • EnsureCreated dipanggil untuk DbContext membuat database SQLite jika belum ada. Ini adalah cara cepat untuk membuat database saat membuat prototipe atau menguji aplikasi. Namun, jika model berubah, maka database perlu dihapus sehingga dapat dibuat lagi. EnsureDeleted(Baris dapat tidak dikomentari untuk menghapus dan membuat ulang database dengan mudah saat aplikasi dijalankan.) Anda mungkin ingin menggunakan Migrasi Inti EF untuk memodifikasi dan memperbarui skema database tanpa kehilangan data apa pun.
  • EnsureCreated juga akan mengisi database baru dengan data yang ditentukan dalam ProductsContext.OnModelCreating metode .
  • Metode Load ekstensi digunakan untuk memuat semua kategori dari database ke DbContextdalam . Entitas ini sekarang akan dilacak oleh DbContext, yang akan mendeteksi perubahan apa pun yang dibuat ketika kategori diedit oleh pengguna.
  • Properti categoryBindingSource.DataSource diinisialisasi ke kategori yang sedang dilacak oleh DbContext. Ini dilakukan dengan memanggil Local.ToBindingList() properti CategoriesDbSet . Local menyediakan akses ke tampilan lokal kategori yang dilacak, dengan peristiwa yang dikaitkan untuk memastikan data lokal tetap sinkron dengan data yang ditampilkan, dan sebaliknya. ToBindingList()mengekspos data ini sebagai IBindingList, yang dipahami oleh pengikatan data Formulir Windows.

Metode OnClosing ini dipanggil ketika formulir ditutup. Saat ini, DbContext dibuang, yang memastikan sumber daya database apa pun akan dibebaskan, dan dbContext bidang diatur ke null sehingga tidak dapat digunakan lagi.

Mengisi tampilan Produk

Jika aplikasi dimulai pada saat ini, maka aplikasi akan terlihat seperti ini:

Fist run of the application

Perhatikan bahwa kategori telah dimuat dari database, tetapi tabel produk tetap kosong. Selain itu, tombol Simpan tidak berfungsi.

Untuk mengisi tabel produk, EF Core perlu memuat produk dari database untuk kategori yang dipilih. Untuk mencapai ini:

  1. Di perancang untuk formulir utama, pilih DataGridView untuk kategori.

  2. Di Properti untuk DataGridView, pilih peristiwa (tombol kilat), dan klik dua kali peristiwa SelectionChanged.

    Add the SelectionChanged event

    Ini akan membuat stub dalam kode formulir utama untuk peristiwa yang akan diaktifkan setiap kali pemilihan kategori berubah.

  3. Isi kode untuk peristiwa:

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

Dalam kode ini, jika ada sesi aktif (non-null), DbContext maka kita mendapatkan Category instans yang terikat ke baris yang saat ini dipilih dari DataViewGrid. (Ini mungkin null jika baris akhir dalam tampilan dipilih, yang digunakan untuk membuat kategori baru.) Jika ada kategori yang dipilih, maka DbContext diinstruksikan untuk memuat produk yang terkait dengan kategori tersebut. Hal ini dilakukan dengan:

  • EntityEntry Mendapatkan untuk Category instans (dbContext.Entry(category))
  • Memberi tahu EF Core bahwa kami ingin beroperasi pada navigasi pengumpulan () tersebut ProductsCategory (.Collection(e => e.Products))
  • Dan akhirnya memberi tahu EF Core bahwa kami ingin memuat koleksi produk tersebut dari database (.Load();)

Tip

Ketika Load dipanggil, EF Core hanya akan mengakses database untuk memuat produk jika belum dimuat.

Jika aplikasi sekarang dijalankan lagi, maka aplikasi harus memuat produk yang sesuai setiap kali kategori dipilih:

Products are loaded

Menyimpan perubahan

Terakhir, tombol Simpan dapat disambungkan ke EF Core sehingga setiap perubahan yang dilakukan pada produk dan kategori disimpan ke database.

  1. Di perancang untuk formulir utama, pilih tombol Simpan .

  2. Di Properti untuk Button, pilih peristiwa (tombol kilat), dan klik dua kali peristiwa Klik.

    Add the Click event for Save

  3. Isi kode untuk peristiwa:

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

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

Kode ini memanggil SaveChanges pada DbContext, yang menyimpan perubahan apa pun yang dilakukan pada database SQLite. Jika tidak ada perubahan yang dilakukan, maka ini adalah no-op, dan tidak ada panggilan database yang dilakukan. Setelah disimpan, DataGridView kontrol akan disegarkan. Ini karena EF Core membaca nilai kunci utama yang dihasilkan untuk produk dan kategori baru apa pun dari database. Memanggil Refresh pembaruan tampilan dengan nilai yang dihasilkan ini.

Aplikasi akhir

Berikut adalah kode lengkap untuk formulir utama:

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

Aplikasi sekarang dapat dijalankan, dan produk dan kategori dapat ditambahkan, dihapus, dan diedit. Perhatikan bahwa jika tombol Simpan diklik sebelum menutup aplikasi, maka setiap perubahan yang dilakukan akan disimpan dalam database dan dimuat ulang saat aplikasi dimulai kembali. Jika Simpan tidak diklik, perubahan apa pun akan hilang saat aplikasi dimulai kembali.

Tip

Kategori atau produk baru dapat ditambahkan ke DataViewControl menggunakan baris kosong di bagian bawah kontrol. Baris dapat dihapus dengan memilihnya dan menekan tombol Del .

Sebelum menyimpan

The running application before clicking Save

Setelah menyimpan

The running application after clicking Save

Perhatikan bahwa nilai kunci utama untuk kategori dan produk yang ditambahkan diisi saat Simpan diklik.

Pelajari lebih lanjut