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
Abra o Visual Studio
Na tela Iniciar, selecione Criar projeto.
Escolha Aplicativo do Windows Forms e, em seguida, Avançar.
Na próxima tela, dê um nome ao projeto, por exemplo, GetStartedWinForms, e selecione Avançar.
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.
Escolha Criar.
Instalar os pacotes NuGet do EF Core
Clique com o botão direito do mouse na solução e escolha Gerenciar Pacotes NuGet para Solução...
Escolha a guia Navegar e procure por "Microsoft.EntityFrameworkCore.Sqlite".
Selecione o pacote Microsoft.EntityFrameworkCore.Sqlite.
Verifique o projeto GetStartedWinForms no painel direito.
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.
Clique em Instalar
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.
Clique com o botão direito do mouse no projeto e escolha Adicionar e, em seguida, Classe... para adicionar uma nova classe.
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!; }
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.
Altere o nome do formulário principal de
Form1
paraMainForm
.E altere o título para "Produtos e Categorias".
Usando a Caixa de Ferramentas, adicione dois controles
DataGridView
, dispostos um ao lado do outro.Nas Propriedades do primeiro
DataGridView
, altere o Nome paradataGridViewCategories
.Nas Propriedades do segundo
DataGridView
, altere o Nome paradataGridViewProducts
.Usando também a Caixa de Ferramentas, adicione um controle
Button
.Nomeie o botão
buttonSave
e dê a ele o texto "Salvar". O formulário deve ter a seguinte aparência:
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.
Clique no Glifo de Ação do Designer no primeiro
DataGridView
. Esse é o pequeno botão no canto superior direito do controle.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....
Escolha Categoria para criar uma fonte de dados de objeto para categorias e clique em OK.
Dica
Se nenhum tipo de fonte de dados aparecer aqui, verifique se
Product.cs
,Category.cs
eProductsContext.cs
foram adicionados ao projeto e se a solução foi criada.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.
O segundo
DataGridView
será associado a produtos. No entanto, em vez de ser associado ao tipoProduct
de nível superior, ele será associado à navegação deProducts
a partir da associaçãoCategory
do primeiroDataGridView
. Isso significa que, quando uma categoria é selecionada na primeira exibição, os produtos dessa categoria serão automaticamente usados na segunda exibição.Usando o Glifo de Ação do Designer no segundo
DataGridView
, escolha Escolher Fonte de Dados e, em seguida, expandacategoryBindingSource
e escolhaProducts
.
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.
Clique com o botão direito do mouse na primeira
DataGridView
e selecione Editar Colunas....Torne a coluna
CategoryId
, que representa a chave primária, somente leitura e clique em OK.Clique com o botão direito do mouse na segunda
DataGridView
e escolha Editar Colunas.... Torne a colunaProductId
somente leitura e remova as colunasCategoryId
eCategory
, em seguida, clique em OK.
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.
Abra o código
MainForm
clicando com o botão direito do mouse no arquivo e escolhendo Exibir Código.Adicione um campo privado para manter o
DbContext
da sessão e adicione substituições para os métodosOnLoad
eOnClosing
. 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 noDbContext
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 linhaEnsureDeleted
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étodoProductsContext.OnModelCreating
.- O método de extensão
Load
é usado para carregar todas as categorias do banco de dados noDbContext
. Essas entidades agora serão rastreadas peloDbContext
, 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 peloDbContext
. Isso é feito chamandoLocal.ToBindingList()
na propriedadeCategories
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 umaIBindingList
, 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:
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:
No designer do formulário principal, selecione a
DataGridView
para as categorias.Nas Propriedades do
DataGridView
, escolha os eventos (o botão de raio) e clique duas vezes no 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.
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ânciaCategory
(dbContext.Entry(category)
) - Informar ao EF Core que se quer operar na navegação de coleção
Products
desseCategory
(.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:
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.
No designer do formulário principal, selecione o botão Salvar.
Nas Propriedades do
Button
, escolha os eventos (o botão de raio) e clique duas vezes no evento Clicar.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
Após salvar
Observe que os valores da chave primária para a categoria e os produtos adicionados são preenchidos ao clicar em Salvar.