Partilhar via


Introdução ao Windows Forms

Este passo a passo mostra como criar um aplicativo simples do Windows Forms (WinForms) apoiado por um banco de dados SQLite. O aplicativo usa o Entity Framework Core (EF Core) para carregar dados do banco de dados, controlar as alterações feitas nesses dados e manter essas alterações de volta ao banco de dados.

As capturas de tela e listagens de código neste passo a passo são retiradas do Visual Studio 2022 17.3.0.

Sugestão

Você pode ver o exemplo de deste artigo no GitHub.

Pré-requisitos

Você precisa ter o Visual Studio 2022 17.3 ou posterior instalado com a carga de trabalho da área de trabalho .NET selecionada para concluir este passo a passo. Para obter mais informações sobre como instalar a versão mais recente do Visual Studio, consulte Instalar o Visual Studio.

Criar o aplicativo

  1. Abrir o Visual Studio

  2. Na janela Iniciar, escolha Criar novo projeto.

  3. Escolha Aplicação Windows Forms e, em seguida, escolha Avançar.

    Criar um novo projeto do Windows Forms

  4. Na próxima tela, dê um nome ao projeto, por exemplo, GetStartedWinForms, e escolha Next.

  5. Na próxima tela, escolha a versão .NET a ser usada. Este passo a passo foi criado com o .NET 7, mas também deve funcionar com versões posteriores.

  6. Selecione Criar.

Instalar os pacotes NuGet do EF Core

  1. Clique com o botão direito do mouse na solução e escolha Gerenciar pacotes NuGet para solução...

    Gerenciar pacotes NuGet para solução

  2. Escolha a guia Procurar e procure por "Microsoft.EntityFrameworkCore.Sqlite".

  3. Selecione o pacote Microsoft.EntityFrameworkCore.Sqlite .

  4. Verifique o projeto GetStartedWinForms no painel direito.

  5. Escolha a versão mais recente. Para usar uma versão de pré-lançamento, verifique se a caixa Incluir pré-lançamento está marcada.

  6. Clique em Instalar

    Instale o pacote Microsoft.EntityFrameworkCore.Sqlite

Observação

O Microsoft.EntityFrameworkCore.Sqlite é o pacote "provedor de banco de dados" para usar o EF Core com um banco de dados SQLite. Pacotes semelhantes estão disponíveis para outros sistemas de banco de dados. A instalação de um pacote de provedor de banco de dados traz automaticamente todas as dependências necessárias para usar o EF Core com esse sistema de banco de dados. Isso inclui o pacote base Microsoft.EntityFrameworkCore .

Definir um modelo

Neste passo a passo, implementaremos um modelo usando "Code First". Isso significa que o EF Core criará as tabelas e o esquema do banco de dados com base nas classes C# que você definir. Consulte Gerenciando esquemas de banco de dados para ver como usar um banco de dados existente.

  1. Clique com o botão direito do mouse no projeto e escolha Adicionar e, em seguida, Classe... para adicionar uma nova classe.

    Adicionar nova classe

  2. Use o nome do arquivo Product.cs e substitua o código da classe por:

    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. Repita para criar Category.cs com o seguinte código:

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

A Products propriedade na Category classe e a Category propriedade na Product classe são chamadas de "navegações". No EF Core, as navegações definem uma relação entre dois tipos de entidade. Neste caso, a Product.Category navegação faz referência à categoria a que pertence um determinado produto. Da mesma forma, a navegação da coleção Category.Products inclui todos os produtos de uma determinada categoria.

Sugestão

Ao usar Windows Forms, o ObservableCollectionListSource, que implementa IListSource, pode ser utilizado para as navegações de coleção. Isso não é necessário, mas melhora a experiência de vinculação de dados bidirecional.

Definir o DbContext

No EF Core, uma classe derivada de DbContext é usada para configurar tipos de entidade num modelo e para agir como uma sessão para interagir com o banco de dados. No caso mais simples, uma DbContext classe:

  • Contém DbSet propriedades para cada tipo de entidade no modelo.
  • Substitui o método OnConfiguring para configurar o fornecedor de banco de dados e a cadeia de ligação a utilizar. Consulte Configurando um DbContext para obter mais informações.

Nesse caso, a classe DbContext também substitui o OnModelCreating método para fornecer alguns dados de exemplo para o aplicativo.

Adicione uma nova ProductsContext.cs classe ao projeto com o seguinte código:

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

Certifique-se de criar a solução neste momento.

Adicionando controles ao formulário

O aplicativo mostrará uma lista de categorias e uma lista de produtos. Quando uma categoria é selecionada na primeira lista, a segunda lista é alterada para mostrar produtos para essa categoria. Essas listas podem ser modificadas para adicionar, remover ou editar produtos e categorias, e essas alterações podem ser salvas no banco de dados SQLite clicando em um botão Salvar .

  1. Altere o nome do formulário principal de Form1 para MainForm.

    Renomeie o Form1 para MainForm

  2. E altere o título para "Produtos e Categorias".

    Título MainForm como

  3. Usando a Caixa de Ferramentas, adicione dois DataGridView controles, dispostos um ao lado do outro.

    Adicionar DataGridView

  4. Nas Propriedades do primeiro DataGridView, altere o Nome para dataGridViewCategories.

  5. Nas Propriedades do segundo DataGridView, altere o Nome para dataGridViewProducts.

  6. Também usando a Caixa de Ferramentas, adicione um Button controle.

  7. Nomeie o botão buttonSave e dê-lhe o texto "Salvar". O formulário deve ter a seguinte aparência:

    Layout do formulário

Vinculação de dados

A próxima etapa é conectar os Product tipos e Category do modelo aos DataGridView controles. Isso vinculará os dados carregados pelo EF Core aos controles, de modo que as entidades rastreadas pelo EF Core sejam mantidas em sincronia com as exibidas nos controles.

  1. Clique no Glifo de Ação do Designer no primeiro DataGridView. Este é o pequeno botão no canto superior direito do controle.

    O Glifo de Ação do Designer

  2. Isso abre a Lista de Ações, onde se pode aceder à lista suspensa Escolher Fonte de Dados. Ainda não criamos uma fonte de dados, então vá para a parte inferior e escolha Adicionar nova fonte de dados de objeto....

    Adicionar nova fonte de dados de objeto

  3. Escolha Categoria para criar uma fonte de dados de objeto para categorias e clique em OK.

    Escolha o tipo de fonte de dados da categoria

    Sugestão

    Se nenhum tipo de fonte de dados aparecer aqui, certifique-se de que Product.cs, Category.cs e ProductsContext.cs foram adicionados ao projeto e a solução foi criada.

  4. Agora, a lista suspensa Escolher fonte de dados contém a fonte de dados do objeto que acabamos de criar. Expanda Outras Fontes de Dados, Fontes de Dados do Projeto e escolha Categoria.

    Escolha a fonte de dados de Categoria

    O segundo DataGridView será vinculado aos produtos. No entanto, em vez de ligar-se ao tipo Product de nível superior, ele será ligado à navegação Products a partir da ligação Category do primeiro DataGridView. Isto significa que, quando uma categoria é selecionada na primeira vista, os produtos dessa categoria serão automaticamente utilizados na segunda vista.

  5. Usando o Glifo de Ação do Designer no segundo DataGridView, escolha Escolher Fonte de Dados e, em seguida, expanda o categoryBindingSource e escolha Products.

    Escolha a fonte de dados de produtos

Configurando o que é exibido

Por padrão, uma coluna é criada no DataGridView para cada propriedade dos tipos vinculados. Além disso, os valores para cada uma dessas propriedades podem ser editados pelo usuário. No entanto, alguns valores, como os valores de chave primária, são considerados só de leitura e, portanto, não devem ser editados. Além disso, algumas propriedades, como a propriedade de CategoryId chave estrangeira e a Category navegação, não são úteis para o usuário e, portanto, devem ser ocultadas.

Sugestão

É comum ocultar propriedades de chave primária em um aplicativo real. Eles são deixados visíveis aqui para facilitar a visão do que o EF Core está fazendo nos bastidores.

  1. Clique com o botão direito do mouse na primeira DataGridView e escolha Editar colunas....

    Editar colunas DataGridView

  2. Torne a CategoryId coluna, que representa a chave primária, somente leitura e clique em OK.

    Tornar a coluna CategoryId só de leitura

  3. Clique com o botão direito do mouse no segundo DataGridView e escolha Editar colunas.... Torne a ProductId coluna somente leitura e remova as CategoryId colunas e Category e clique em OK.

    Tornar a coluna ProductId somente leitura e remover as colunas CategoryId e Category

Conectando-se ao EF Core

O aplicativo agora precisa de uma pequena quantidade de código para conectar o EF Core aos controles ligados a dados.

  1. Abra o MainForm código clicando com o botão direito do rato no ficheiro e escolhendo Ver Código.

    Ver código

  2. Adicione um campo privado para armazenar o DbContext da sessão e adicione substituições para os métodos OnLoad e OnClosing. O código deve ter esta aparência:

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

O OnLoad método é chamado quando o formulário é carregado. Neste momento

  • É criada uma instância do ProductsContext que será usada para carregar e controlar alterações em produtos e categorias exibidas pelo aplicativo.
  • EnsureCreated é chamado no DbContext para criar o banco de dados SQLite se ele ainda não existir. Esta é uma maneira rápida de criar um banco de dados ao prototipar ou testar aplicativos. No entanto, se o modelo for alterado, o banco de dados precisará ser excluído para que possa ser criado novamente. (A EnsureDeleted linha pode ser descomentada para excluir e recriar facilmente o banco de dados quando o aplicativo é executado.) Em vez disso, você pode usar as migrações do EF Core para modificar e atualizar o esquema do banco de dados sem perder dados.
  • EnsureCreated também preencherá o novo banco de dados com os dados definidos no ProductsContext.OnModelCreating método.
  • O Load método de extensão é usado para carregar todas as categorias do banco de dados para o DbContext. Essas entidades agora serão rastreadas pelo DbContext, que detetará quaisquer alterações feitas quando as categorias forem editadas pelo usuário.
  • A categoryBindingSource.DataSource propriedade é inicializada para as categorias que estão sendo rastreadas pelo DbContext. Isso é feito chamando Local.ToBindingList() na propriedade CategoriesDbSet. Local fornece acesso a uma visualização local das categorias rastreadas, com eventos conectados para garantir que os dados locais permaneçam sincronizados com os dados exibidos e vice-versa. ToBindingList() expõe esses dados como um IBindingList, que é entendido pela associação de dados do Windows Forms.

O OnClosing método é chamado quando o formulário é fechado. Neste momento, o DbContext é descartado, que garante que quaisquer recursos do banco de dados serão liberados, e o campo dbContext é definido como nulo para que não possa ser usado novamente.

Preencher a vista Produtos

Se o aplicativo for iniciado neste ponto, então ele deve ter a seguinte aparência:

Primeira execução do aplicativo

Observe que as categorias foram carregadas do banco de dados, mas a tabela de produtos permanece vazia. Além disso, o botão Salvar não funciona.

Para preencher a tabela de produtos, o EF Core precisa carregar produtos do banco de dados para a categoria selecionada. Para o conseguir:

  1. No designer do formulário principal, selecione o DataGridView para categorias.

  2. Nas Propriedades do DataGridView, escolha os eventos (o botão de relâmpago) e clique duas vezes no evento de SelectionChanged.

    Adicionar o evento SelectionChanged

    Isso criará um stub no código do formulário principal para um evento ser acionado sempre que houver alteração na seleção de categoria.

  3. Preencha o código do evento:

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

Neste código, se houver uma sessão ativa (não nula) DbContext, obtemos a instância Category vinculada à linha selecionada atualmente do DataViewGrid. (Isso pode ocorrer null se a linha final do modo de exibição estiver selecionada, que é usada para criar novas categorias.) Se houver uma categoria selecionada, então o DbContext é instruído a carregar os produtos associados a essa categoria. Isto é feito por:

  • Obtendo um EntityEntry para a Category instância (dbContext.Entry(category))
  • Informar ao EF Core que queremos operar na navegação da Products coleção desse Category (.Collection(e => e.Products))
  • E, finalmente, dizendo ao EF Core que queremos carregar essa coleção de produtos do banco de dados (.Load();)

Sugestão

Quando Load é chamado, o EF Core só acessará o banco de dados para carregar os produtos se eles ainda não tiverem sido carregados.

Se o aplicativo for executado novamente, ele deverá carregar os produtos apropriados sempre que uma categoria for selecionada:

Os produtos são carregados

Guardar alterações

Finalmente, o botão Salvar pode ser conectado ao EF Core para que todas as alterações feitas nos produtos e categorias sejam salvas no banco de dados.

  1. No designer do formulário principal, selecione o botão Salvar .

  2. Nas Propriedades do Button, escolha os eventos (o botão relâmpago) e clique duas vezes no evento Click .

    Adicionar o evento Click para Salvar

  3. Preencha o código do evento:

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

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

Esse código chama SaveChanges no DbContext, que salva quaisquer alterações feitas no banco de dados SQLite. Se nenhuma alteração foi feita, então este é um no-op, e nenhuma chamada de banco de dados é feita. Depois de salvar, os DataGridView controles são atualizados. Isso ocorre porque o EF Core lê valores de chave primária gerados para quaisquer novos produtos e categorias do banco de dados. A chamada Refresh atualiza a exibição com esses valores gerados.

Aplicação final

Aqui está o código completo para o formulário principal:

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

O aplicativo agora pode ser executado e produtos e categorias podem ser adicionados, excluídos e editados. Observe que, se o botão Salvar for clicado antes de fechar o aplicativo, todas as alterações feitas serão armazenadas no banco de dados e recarregadas quando o aplicativo for reiniciado. Se Salvar não for clicado, todas as alterações serão perdidas quando o aplicativo for reiniciado.

Sugestão

Uma nova categoria ou produto pode ser adicionado a um DataViewControl usando a linha vazia na parte inferior do controle. Uma linha pode ser excluída selecionando-a e pressionando a tecla Del .

Antes de guardar

O aplicativo em execução antes de clicar em Salvar

Depois de guardar

O aplicativo em execução depois de clicar em Salvar

Observe que os valores de chave primária para a categoria e os produtos adicionados são preenchidos quando Salvar é clicado.

Mais informações