Compartilhar via


Introdução ao Windows Forms

Este passo a passo mostra como criar um aplicativo simples do Windows Forms (WinForms) com o suporte de um banco de dados SQLite. O aplicativo usa o Entity Framework Core (EF Core) para carregar dados do banco de dados, rastrear as alterações feitas nesses dados e persistir essas alterações no banco de dados.

As capturas de tela e as listas de códigos neste passo a passo foram tiradas do Visual Studio 2022 17.3.0.

Dica

Veja o exemplo deste artigo no GitHub.

Pré-requisitos

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

Criar o aplicativo

  1. Abra o Visual Studio

  2. Na tela Iniciar, selecione Criar projeto.

  3. Escolha Aplicativo do Windows Forms e, em seguida, Avançar.

    Criar um novo projeto dos Windows Forms

  4. Na próxima tela, dê um nome ao projeto, por exemplo, GetStartedWinForms, e selecione Avançar.

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

  6. Escolha 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 a Solução

  2. Escolha a guia Navegar 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 básico Microsoft.EntityFrameworkCore.

Definir um Modelo

Neste passo a passo, implementaremos um modelo usando o "Code First". Isso significa que o EF Core criará as tabelas de dados e o esquema com base nas classes C# que você definir. Confira Gerenciar Esquemas de Banco de Dados para saber 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 de 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 propriedade Products na classe Category e a propriedade Category na classe Product são chamadas de "navegações". No EF Core, as navegações definem uma relação entre dois tipos de entidades. Nesse caso, a navegação Product.Category faz referência à categoria à qual um determinado produto pertence. Da mesma forma, a navegação de coleção Category.Products contém todos os produtos de uma determinada categoria.

Dica

Ao usar o Windows Forms, o ObservableCollectionListSource, que implementa o IListSource, pode ser usado para 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 em um modelo e atuar como uma sessão para interagir com o banco de dados. No caso mais simples, uma classe DbContext:

  • Contém propriedades DbSet para cada tipo de entidade no modelo.
  • Substitui o método OnConfiguring para configurar o provedor de banco de dados e a cadeia de conexão a serem usados. Confira Configuração de um DbContext para obter mais informações.

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

Adicione uma nova classe ProductsContext.cs 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" });
    }
}

Crie a solução nesse momento.

Adicionando controles ao formulário

O aplicativo mostrará uma lista de categorias e uma lista de produtos. Quando uma categoria for selecionada na primeira lista, a segunda lista será alterada para mostrar os produtos dessa 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 no botão Salvar.

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

    Renomear 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 controles DataGridView, 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. Usando também a Caixa de Ferramentas, adicione um controle Button.

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

    Layout de formulários

Vinculação de dados

A próxima etapa é conectar os tipos Product e Category do modelo aos controles de DataGridView. 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. Esse é 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, na qual o menu suspenso para Escolher Fonte de Dados pode ser acessado. Ainda não criamos uma fonte de dados, portanto, vá até a parte inferior e selecione Adicionar Nova Fonte de Dados do 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.

    Escolher tipo de fonte de dados de Categoria

    Dica

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

  4. Agora, o menu suspenso Escolher Fonte de Dados contém a fonte de dados de objeto que acabamos de criar. Expanda Outras Fontes de Dados, em seguida, Fontes de Dados do Projeto e escolha Categoria.

    Escolher fonte de dados de Categoria

    O segundo DataGridView será associado a produtos. No entanto, em vez de ser associado ao tipo Product de nível superior, ele será associado à navegação de Products a partir da associação Category do primeiro DataGridView. Isso significa que, quando uma categoria é selecionada na primeira exibição, os produtos dessa categoria serão automaticamente usados na segunda exibição.

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

    Escolher fonte de dados de Produtos

Configuração do que é exibido

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

Dica

É comum ocultar as propriedades da chave primária em um aplicativo real. Elas são deixadas visíveis aqui para facilitar a visualização do que o EF Core está fazendo nos bastidores.

  1. Clique com o botão direito do mouse na primeira DataGridView e selecione Editar Colunas....

    Editar colunas DataGridView

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

    Tornar a coluna CategoryId somente leitura

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

    Torne a coluna ProductId somente leitura e remova as colunas CategoryId e Categoria

Conectar-se ao EF Core

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

  1. Abra o código MainForm clicando com o botão direito do mouse no arquivo e escolhendo Exibir Código.

    Exibir Código

  2. Adicione um campo privado para manter o DbContext da sessão e adicione substituições para os métodos OnLoad e OnClosing. Seu código deve ficar assim:

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 método OnLoad é chamado quando o formulário é carregado. Nesse momento

  • É criada uma instância do ProductsContext que será usado para carregar e rastrear alterações nos produtos e categorias exibidos pelo aplicativo.
  • EnsureCreated é chamado no DbContext para criar o banco de dados SQLite, se ele ainda não existir. Essa é uma maneira rápida de criar um banco de dados ao criar protótipos 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 linha EnsureDeleted pode ser descomentada para excluir e recriar facilmente o banco de dados quando o aplicativo for executado.) Em vez disso, talvez você queira usar o EF Core Migrations para modificar e atualizar o esquema do banco de dados sem perder nenhum dado.
  • EnsureCreated também preencherá o novo banco de dados com os dados definidos no método ProductsContext.OnModelCreating.
  • O método de extensão Load é usado para carregar todas as categorias do banco de dados no DbContext. Essas entidades agora serão rastreadas pelo DbContext, que detectará as alterações feitas quando as categorias forem editadas pelo usuário.
  • A propriedade categoryBindingSource.DataSource é inicializada com as categorias que estão sendo rastreadas pelo DbContext. Isso é feito chamando Local.ToBindingList() na propriedade Categories DbSet. Local fornece acesso a uma exibição local das categorias rastreadas, com eventos conectados para garantir que os dados locais permaneçam em sincronia com os dados exibidos e vice-versa. ToBindingList() expõe esses dados como uma IBindingList, que é compreendida pela associação de dados do Windows Forms.

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

Preenchimento do modo de exibição Produtos

Se o aplicativo for iniciado nesse ponto, ele deverá 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 isso:

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

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

    Adicionar o evento SelectionChanged

    Isso criará um stub no código do formulário principal para que um evento seja disparado sempre que a seleção de categoria for alterada.

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

Nesse código, se houver uma sessão DbContext (não nula) ativa, obteremos a instância Category associada à linha atualmente selecionada do arquivo DataViewGrid. (Isso pode ser null se a última linha da exibição for selecionada, o que é usado para criar novas categorias.) Se houver uma categoria selecionada, o DbContext é instruído a carregar os produtos associados a essa categoria. Isso é feito:

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

Dica

Quando Load for 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

Salvando alterações

Por fim, 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 de raio) e clique duas vezes no evento Clicar.

    Adicionar o evento Clique 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 todas as alterações feitas no banco de dados SQLite. Se nenhuma alteração foi feita, não haverá opção e nenhuma chamada ao banco de dados será feita. Após salvar, os controles DataGridView são atualizados. Isso ocorre porque o EF Core lê os valores de chave primária gerados para todos os novos produtos e categorias do banco de dados. A chamada Refresh atualiza a exibição com esses valores gerados.

O aplicativo final

Veja o código completo do 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();
        }
    }
}

Agora, o aplicativo pode ser executado e os 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, as alterações serão perdidas quando o aplicativo for reiniciado.

Dica

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 salvar

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

Após salvar

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

Observe que os valores da chave primária para a categoria e os produtos adicionados são preenchidos ao clicar em Salvar.

Saiba mais