Поделиться через


Приступая к работе с Windows Forms

В этом пошаговом руководстве показано, как создать простое приложение Windows Forms (WinForms), поддерживаемое базой данных SQLite. Приложение использует Entity Framework Core (EF Core) для загрузки данных из базы данных, отслеживания изменений, внесенных в эти данные, и сохранения этих изменений обратно в базу данных.

Снимки экрана и описания кода в этом пошаговом руководстве взяты из Visual Studio 2022 17.3.0.

Совет

Вы можете скачать используемый в этой статье пример из репозитория GitHub.

Требования

Для выполнения этого пошагового руководства необходимо установить Visual Studio 2022 17.3 или более поздней версии с рабочей нагрузкой .NET для настольных приложений. Дополнительные сведения об установке новейшей версии Visual Studio см. в статье Установка Visual Studio.

Создание приложения

  1. Запустите Visual Studio

  2. На начальном экране выберите Создать проект.

  3. Выберите приложение Windows Forms и нажмите кнопку "Далее".

    Создание проекта Windows Forms

  4. На следующем экране укажите имя проекта, например GetStartedWinForms и нажмите кнопку "Далее".

  5. На следующем экране выберите используемую версию .NET. Это пошаговое руководство было создано с помощью .NET 7, но оно также должно работать с более поздними версиями.

  6. Выберите Создать.

Установка пакетов NuGet EF Core

  1. Щелкните правой кнопкой мыши решение и выберите Управление пакетами NuGet для решения...

    Управление пакетами NuGet для решения

  2. Перейдите на вкладку "Обзор" и найдите "Microsoft.EntityFrameworkCore.Sqlite".

  3. Выберите пакет Microsoft.EntityFrameworkCore.Sqlite.

  4. Проверьте проект GetStartedWinForms в правой области.

  5. Выберите последнюю версию. Чтобы использовать предварительную версию, убедитесь, что установлен флажок «Включить предварительную версию».

  6. Щелкните Установить.

    Установка пакета Microsoft.EntityFrameworkCore.Sqlite

Примечание.

Microsoft.EntityFrameworkCore.Sqlite — это пакет поставщика базы данных для использования EF Core с базой данных SQLite. Аналогичные пакеты доступны для других систем баз данных. Установка пакета поставщика базы данных автоматически приводит все зависимости, необходимые для использования EF Core с этой системой базы данных. Сюда входит базовый пакет Microsoft.EntityFrameworkCore .

Определение модели

В этом пошаговом руководстве мы реализуем модель с помощью Code First. Таким образом, EF Core создаст таблицы и схему базы данных на основе определенных вами классов C#. См. статью "Управление схемами баз данных" , чтобы узнать, как использовать существующую базу данных.

  1. Щелкните проект правой кнопкой мыши и нажмите кнопку "Добавить", а затем "Класс" , чтобы добавить новый класс.

    Добавление нового класса

  2. Используйте имя Product.cs файла и замените код для класса следующим образом:

    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. Повторите создание Category.cs с помощью следующего кода:

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

Свойство Products в классе Category и свойство Category в классе Product называются навигационными свойствами. В EF Core навигации определяют связь между двумя типами сущностей. В этом случае навигация Product.Category ссылается на категорию, к которой принадлежит данный продукт. Аналогичным образом навигация Category.Products по коллекции содержит все продукты для данной категории.

Совет

При использовании Windows Forms можно использовать ObservableCollectionListSource, который реализует IListSource, для навигации по коллекциям. Это не обязательно, но это улучшает двустороннюю привязку данных.

Определение DbContext

В EF Core класс, производный от DbContext, используется для настройки типов сущностей в модели и выступает в качестве сеанса для взаимодействия с базой данных. В самом простом случае класс DbContext:

  • Содержит DbSet свойства для каждого типа сущности в модели.
  • Переопределяет метод OnConfiguring для конфигурации поставщика базы данных и строки подключения для использования. Дополнительные сведения см. в разделе "Настройка DbContext ".

В этом случае класс DbContext также переопределяет метод OnModelCreating, чтобы предоставить некоторый образец данных для приложения.

Добавьте новый ProductsContext.cs класс в проект со следующим кодом:

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

Обязательно создайте решение на этом этапе.

Добавление элементов управления на форму

Приложение отобразит список категорий и список продуктов. Если в первом списке выбрана категория, то второй список изменится, чтобы отобразить продукты для этой категории. Эти списки можно изменить, чтобы добавить, удалить или изменить продукты и категории, и эти изменения можно сохранить в базе данных SQLite, нажав кнопку "Сохранить ".

  1. Измените имя основной формы на Form1MainForm.

    Переименование формы1 в MainForm

  2. И измените заголовок на "Продукты и категории".

    Установите заголовок MainForm как

  3. С помощью панели элементов добавьте два DataGridView элемента управления, расположенные рядом друг с другом.

    Добавить DataGridView

  4. В свойствах для первого DataGridView измените имя на dataGridViewCategories.

  5. В свойствах этого второго DataGridViewизмените Имя на dataGridViewProducts.

  6. Кроме того, с помощью панели элементов добавьте Button элемент управления.

  7. Назовите кнопку buttonSave и присвойте ей текст "Сохранить". Форма должна выглядеть следующим образом:

    Макет формы

Привязка данных

Следующим шагом является подключение типов Product и Category из модели к элементам управления DataGridView. Это привязывает данные, загруженные EF Core, к элементам управления, таким образом, что сущности, отслеживаемые EF Core, синхронизируются с данными, отображаемыми в элементах управления.

  1. Щелкните Глиф операции конструктора на первом элементеDataGridView. Это крошечная кнопка в правом верхнем углу элемента управления.

    Значок действия конструктора

  2. Откроется список действий, из которого можно получить доступ к раскрывающемся списку для выбранного источника данных. Мы еще не создали источник данных, поэтому перейдите к нижнему краю и нажмите кнопку "Добавить новый источник данных объекта...".

    Добавление нового источника данных объекта

  3. Выберите категорию , чтобы создать источник данных объекта для категорий и нажмите кнопку "ОК".

    Выберите тип источника данных категории

    Совет

    Если здесь нет типов источников данных, убедитесь, что , и добавлены в проект и решение собрано.

  4. Теперь раскрывающийся список "Выбор источника данных" содержит только что созданный источник данных объекта. Разверните другие источники данных, а затем выберите "Источники данных проекта" и выберите категорию.

    Выбор источника данных категории

    Второй DataGridView будет привязан к продуктам. Однако вместо привязки к типу верхнего уровня Product она будет привязана к Products навигации из Category привязки первой DataGridView. Это означает, что при выборе категории в первом представлении продукты для этой категории будут автоматически использоваться во втором представлении.

  5. Используя значок Designer Action на втором DataGridView, выберите Выбрать источник данных, затем раскройте categoryBindingSource и выберите Products.

    Выбор источника данных

Настройка отображаемых элементов

По умолчанию столбец создается в DataGridView для каждого свойства привязанных типов. Кроме того, значения для каждого из этих свойств можно изменить пользователем. Однако некоторые значения, такие как значения первичного ключа, концептуально доступны только для чтения, и поэтому не следует изменять. Кроме того, некоторые свойства, такие как CategoryId свойство внешнего ключа и Category навигация, не полезны для пользователя, и поэтому их следует скрыть.

Совет

Свойства первичного ключа обычно скрываются в реальных приложениях. Они остаются видимыми здесь, чтобы легко увидеть, что EF Core делает за кулисами.

  1. Щелкните правой кнопкой мыши первый DataGridView и выберите пункт "Изменить столбцы...".

    Изменение столбцов DataGridView

  2. CategoryId Сделайте столбец, представляющий первичный ключ, доступным только для чтения, и нажмите ОК.

    Создание столбца CategoryId только для чтения

  3. Щелкните правой кнопкой мыши второй DataGridView и выберите пункт "Изменить столбцы...". ProductId Сделайте столбец доступным только для чтения и удалите CategoryIdCategory столбцы, а затем нажмите кнопку "ОК".

    Создание столбца ProductId только для чтения и удаление столбцов CategoryId и Category

Подключение к EF Core

Теперь приложению требуется небольшой объем кода для подключения EF Core к элементам управления, привязанным к данным.

  1. MainForm Откройте код, щелкнув файл правой кнопкой мыши и выбрав "Просмотреть код".

    Просмотреть код

  2. Добавьте частное поле для хранения объекта сеанса DbContext, и добавьте переопределения для методов OnLoad и OnClosing. Код должен выглядеть следующим образом:

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

Метод OnLoad вызывается при загрузке формы. В настоящее время

  • Создается экземпляр ProductsContext , который будет использоваться для загрузки и отслеживания изменений в продуктах и категориях, отображаемых приложением.
  • EnsureCreated вызывается на DbContext для создания базы данных SQLite, если она еще не существует. Это быстрый способ создания базы данных при создании прототипов или тестировании приложений. Однако если модель изменяется, необходимо удалить базу данных, чтобы ее можно было создать еще раз. (Строка EnsureDeleted может быть раскомментирована, чтобы легко удалить и заново создать базу данных при запуске приложения.) Вместо этого можно воспользоваться миграциями EF Core, чтобы изменить и обновить структуру базы данных без потери данных.
  • EnsureCreated также заполняет новую базу данных данными, определенными в методе ProductsContext.OnModelCreating .
  • Метод расширения Load используется для загрузки всех категорий из базы данных в DbContext. Теперь эти сущности будут отслеживаться системой DbContext, которая будет обнаруживать любые изменения, внесенные пользователем при редактировании категорий.
  • Свойство categoryBindingSource.DataSource инициализировано категориями, которые отслеживаются с помощью DbContext. Это делается путем вызова Local.ToBindingList() на свойстве CategoriesDbSet. Local предоставляет доступ к локальному представлению отслеживаемых категорий с событиями, которые подключены к локальным данным, чтобы обеспечить синхронизацию локальных данных с отображаемыми данными и наоборот. ToBindingList() предоставляет эти данные как IBindingList, который распознаётся привязкой данных в Windows Forms.

Метод OnClosing вызывается при закрытии формы. В настоящее время удаляется, DbContext что гарантирует, что все ресурсы базы данных будут освобождены, и dbContext поле имеет значение NULL, чтобы его нельзя было использовать снова.

Заполнение представления "Продукты"

Если приложение запущено на этом этапе, оно должно выглядеть примерно так:

Первый запуск приложения

Обратите внимание, что категории были загружены из базы данных, но таблица продуктов остается пустой. Кроме того, кнопка "Сохранить " не работает.

Чтобы заполнить таблицу продуктов, EF Core необходимо загрузить продукты из базы данных для выбранной категории. Чтобы достичь этого, выполните указанные ниже действия.

  1. В конструкторе основной формы выберите DataGridView категории.

  2. В "Свойствах" для , выберите события (кнопка с изображением молнии) и дважды щелкните событие SelectionChanged.

    Добавление события SelectionChanged

    Это создаст заготовку события, которое будет запущено в коде основной формы, когда изменяется выбор категории.

  3. Введите код для события:

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

В этом коде, если существует активный (непустой) сеанс DbContext, мы получаем Category экземпляр, привязанный к текущей выбранной строке DataViewGrid. (Это может быть null , если выбрана окончательная строка в представлении, которая используется для создания новых категорий.) Если выбрана категория, необходимо загрузить продукты, DbContext связанные с этой категорией. Способы выполнения этих целей:

  • Получение EntityEntry для экземпляра Category (dbContext.Entry(category))
  • Предоставление EF Core знать, что мы хотим работать с Products навигацией по коллекции этого Category (.Collection(e => e.Products))
  • И, наконец, сообщите EF Core, что мы хотим загрузить коллекцию продуктов из базы данных (.Load();)

Совет

При Load вызове EF Core будет получать доступ только к базе данных для загрузки продуктов, если они еще не загружены.

Если приложение запущено снова, оно должно загружать соответствующие продукты всякий раз, когда выбрана категория:

Продукты загружаются

Сохранение изменений

Наконец, кнопка "Сохранить" может быть подключена к EF Core, чтобы все изменения, внесенные в продукты и категории, сохранялись в базе данных.

  1. В конструкторе основной формы нажмите кнопку "Сохранить ".

  2. Откройте "Свойства" для Button, выберите события (кнопка молнии) и дважды щелкните событие Click.

    Добавление события click для сохранения

  3. Введите код для события:

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

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

Этот код вызывает SaveChanges на DbContext, что сохраняет все изменения, внесенные в базу данных SQLite. Если изменения не были внесены, это no-op, и вызов базы данных не выполняется. После сохранения DataGridView элементы управления обновляются. Это связано с тем, что EF Core считывает созданные значения первичного ключа для любых новых продуктов и категорий из базы данных. Вызов Refresh обновляет отображение с этими созданными значениями.

Финальное приложение

Ниже приведен полный код основной формы:

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

Теперь приложение можно запускать, а продукты и категории можно добавлять, удалять и изменять. Обратите внимание, что если кнопка "Сохранить " нажимается перед закрытием приложения, все внесенные изменения будут храниться в базе данных и повторно загружаться при повторном запуске приложения. Если элемент "Сохранить " не щелкается, все изменения теряются при повторном запуске приложения.

Совет

Новую категорию или продукт можно добавить в DataViewControl, используя пустую строку в нижней части элемента управления. Строку можно удалить, выбрав ее и нажав клавишу Del .

Перед сохранением

Запущенное приложение перед нажатием кнопки

После сохранения

Запущенное приложение после нажатия кнопки

Обратите внимание, что значения первичного ключа для добавленной категории и продуктов заполняются при нажатии кнопки "Сохранить ".

Подробнее