Compartilhar via


Introdução ao WPF

Este passo a passo mostra como associar tipos POCO a controles do WPF em um formulário de "detalhes principais". O aplicativo usa as APIs do Entity Framework para preencher objetos com dados do banco de dados, controlar alterações e persistir dados no banco de dados.

O modelo define dois tipos que participam da relação um-para-muitos: Categoria (entidade de segurança\principal) e Produto (dependente\detalhe). A estrutura de associação de dados do WPF permite a navegação entre objetos relacionados: selecionar linhas no modo de exibição mestre faz com que a exibição de detalhes seja atualizada com os dados filho correspondentes.

As capturas de tela e as listagens de código neste passo a passo são obtidas do Visual Studio 2019 16.6.5.

Tip

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

Pre-Requisites

Você precisa ter o Visual Studio 2019 16.3 ou posterior instalado com a carga de trabalho da área de trabalho do .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. Abra o Visual Studio
  2. Na janela inicial, escolha Criar novo projeto.
  3. Pesquise "WPF", escolha Aplicativo WPF (.NET) e escolha Avançar.
  4. Na próxima tela, dê um nome ao projeto, por exemplo, GetStartedWPF e escolha Criar.

Instalar os pacotes NuGet do Entity Framework

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

    Gerenciar Pacotes NuGet

  2. Digite entityframeworkcore.sqlite na caixa de pesquisa.

  3. Selecione o pacote Microsoft.EntityFrameworkCore.Sqlite .

  4. Verifique o projeto no painel direito e clique em Instalar

    Pacote sqlite

  5. Repita as etapas para pesquisar entityframeworkcore.proxies e instalar Microsoft.EntityFrameworkCore.Proxies.

Note

Quando você instalou o pacote sqlite, ele retirou automaticamente o pacote base Microsoft.EntityFrameworkCore relacionado. O pacote Microsoft.EntityFrameworkCore.Proxies fornece suporte para dados de "carregamento lento". Isso significa que, quando você tem entidades com entidades filho, somente os pais são buscados na carga inicial. Os proxies detectam quando há uma tentativa de acessar as entidades filho e as carrega automaticamente sob demanda.

Definir um modelo

Neste passo a passo, você implementará um modelo usando "código primeiro". Isso significa que o EF Core criará as tabelas de banco de dados e o esquema com base nas classes C# que você definir.

Adicione uma nova classe. Dê-lhe o nome: Product.cs e preencha-o assim:

Product.cs

namespace GetStartedWPF
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

Em seguida, adicione uma classe nomeada Category.cs e preencha-a com o seguinte código:

Category.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace GetStartedWPF
{
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
}

A propriedade Products na classe Category e a propriedade Category na classe Product são propriedades de navegação. No Entity Framework, as propriedades de navegação fornecem uma maneira de navegar em uma relação entre dois tipos de entidade.

Além de definir entidades, você precisa definir uma classe que deriva de DbContext e expõe as propriedades DbSet<TEntity> . As propriedades DbSet<TEntity> informam ao contexto quais tipos você deseja incluir no modelo.

Uma instância do tipo derivado DbContext gerencia os objetos de entidade durante o tempo de execução, incluindo preencher objetos com dados de um banco de dados, rastreamento de alterações e persistência de dados no banco de dados.

Adicione uma nova ProductContext.cs classe ao projeto com a seguinte definição:

ProductContext.cs

using Microsoft.EntityFrameworkCore;

namespace GetStartedWPF
{
    public class ProductContext : 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");
            optionsBuilder.UseLazyLoadingProxies();
        }
    }
}
  • O DbSet informa ao EF Core quais entidades C# devem ser mapeadas para o banco de dados.
  • Há várias maneiras de configurar o EF Core DbContext. Você pode ler sobre eles em: Configurando um DbContext.
  • Este exemplo usa a OnConfiguring sobrescrição para especificar um arquivo de dados Sqlite.
  • A chamada UseLazyLoadingProxies informa ao EF Core para implementar o carregamento lento, para que as entidades filho sejam carregadas automaticamente quando acessadas a partir do pai.

Pressione CTRL+SHIFT+B ou navegue até Compilar > Solução de compilação para compilar o projeto.

Tip

Saiba mais sobre as diferentes maneiras de manter o banco de dados e os modelos do EF Core em sincronia: Gerenciando Esquemas de Banco de Dados.

Carregamento lento

A propriedade Products na classe Category e a propriedade Category na classe Product são propriedades de navegação. No Entity Framework Core, as propriedades de navegação fornecem uma maneira de navegar em uma relação entre dois tipos de entidade.

O EF Core oferece uma opção de carregar entidades relacionadas do banco de dados automaticamente na primeira vez que você acessar a propriedade de navegação. Com esse tipo de carregamento (chamado de carregamento lento), lembre-se de que na primeira vez que você acessar cada propriedade de navegação, uma consulta separada será executada no banco de dados se o conteúdo ainda não estiver no contexto.

Ao usar tipos de entidade "POCO" (Objeto CRL básico), o EF Core obtém o carregamento lento criando instâncias com tipos de proxy derivados durante o runtime e, em seguida, substituindo as propriedades virtuais em suas classes para adicionar o gancho de carregamento. Para obter o carregamento lento de objetos relacionados, você deve declarar os getters de propriedade de navegação como públicos e virtuais (Overridable no Visual Basic) e sua classe não deve ser selada (NotOverridable no Visual Basic). Ao usar o Database First, as propriedades de navegação são automaticamente tornadas virtuais para habilitar o carregamento lento.

Associar objeto a controles

Adicione as classes definidas no modelo como fontes de dados para este aplicativo WPF.

  1. Clique duas vezes em MainWindow.xaml no Gerenciador de Soluções para abrir o formulário principal

  2. Escolha a guia XAML para editar o XAML.

  3. Imediatamente após a marca Window de abertura, adicione as seguintes fontes para se conectar às entidades do EF Core.

    <Window x:Class="GetStartedWPF.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:GetStartedWPF"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="categoryViewSource"/>
            <CollectionViewSource x:Key="categoryProductsViewSource" 
                                  Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
        </Window.Resources>
    
  4. Isso define a origem para as categorias principais e a origem secundária para os produtos detalhados.

  5. Em seguida, adicione a seguinte marcação ao XAML após a marca de abertura Grid.

    <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" 
              EnableRowVirtualization="True" 
              ItemsSource="{Binding Source={StaticResource categoryViewSource}}" 
              Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding CategoryId}"
                                Header="Category Id" Width="SizeToHeader"
                                IsReadOnly="True"/>
            <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                                Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
    
  6. Note que o CategoryId está definido como ReadOnly porque é atribuído pelo banco de dados e não pode ser alterado.

Adicionando uma tabela de detalhes

Agora que a grade existe para exibir categorias, a grade de detalhes pode ser adicionada para mostrar produtos. Adicione isso dentro do Grid elemento, após o elemento categories DataGrid .

MainWindow.xaml

<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" 
          EnableRowVirtualization="True" 
          ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}" 
          Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected" 
          RenderTransformOrigin="0.488,0.251">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

Por fim, adicione um botão Save e conecte o evento de clique a Button_Click.

<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" 
        Click="Button_Click" Height="20" Width="123"/>

Seu modo de exibição de Design deve ter esta aparência:

Captura de tela do Designer do WPF

Adicionar código que manipula a interação de dados

É hora de adicionar alguns manipuladores de eventos à janela principal.

  1. Na janela XAML, clique no <elemento Janela> para selecionar a janela principal.

  2. Na janela Propriedades , escolha Eventos na parte superior direita e clique duas vezes na caixa de texto à direita do rótulo Carregado .

    Propriedades da janela principal

Isso o leva ao código por trás do formulário e agora editaremos o código para usar o ProductContext a fim de realizar o acesso aos dados. Atualize o código, conforme mostrado abaixo.

O código declara uma instância de execução longa de ProductContext. O ProductContext objeto é usado para consultar e salvar dados no banco de dados. Então, o método Dispose() na instância ProductContext é chamado do método OnClosing substituído. Os comentários de código explicam o que cada etapa faz.

MainWindow.xaml.cs

using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GetStartedWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ProductContext _context =
            new ProductContext();

        private CollectionViewSource categoryViewSource;

        public MainWindow()
        {
            InitializeComponent();
            categoryViewSource =
                (CollectionViewSource)FindResource(nameof(categoryViewSource));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // this is for demo purposes only, to make it easier
            // to get up and running
            _context.Database.EnsureCreated();

            // load the entities into EF Core
            _context.Categories.Load();

            // bind to the source
            categoryViewSource.Source =
                _context.Categories.Local.ToObservableCollection();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // all changes are automatically tracked, including
            // deletes!
            _context.SaveChanges();

            // this forces the grid to refresh to latest values
            categoryDataGrid.Items.Refresh();
            productsDataGrid.Items.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            // clean up database connections
            _context.Dispose();
            base.OnClosing(e);
        }
    }
}

Note

O código usa uma chamada para EnsureCreated() criar o banco de dados na primeira execução. Isso é aceitável em demonstrações, mas em aplicativos de produção você deve conferir as migrações para gerenciar seu esquema. O código também é executado de forma síncrona porque usa um banco de dados SQLite local. Para cenários de produção que normalmente envolvem um servidor remoto, considere o uso das versões assíncronas dos métodos Load e SaveChanges.

Testar o aplicativo WPF

Compile e execute o aplicativo pressionando F5 ou escolhendo Depurar > Iniciar Depuração. O banco de dados deve ser criado automaticamente com um arquivo chamado products.db. Insira um nome de categoria e pressione Enter e adicione produtos à grade inferior. Clique em Salvar e assista à atualização da grade com as IDs fornecidas pelo banco de dados. Realce uma linha e clique em Excluir para remover a linha. A entidade será excluída quando você clicar em Salvar.

Executando o aplicativo

Notificação de alteração de propriedade

Este exemplo depende de quatro etapas para sincronizar as entidades com a interface do usuário.

  1. A chamada _context.Categories.Load() inicial carrega os dados das categorias.
  2. Os proxies de carregamento lento carregam os dados de produtos dependentes.
  3. O controle de alterações interno do EF Core faz as modificações necessárias nas entidades, inclusive inserções e exclusões, ao chamar _context.SaveChanges().
  4. As chamadas para DataGridView.Items.Refresh() forçam um recarregamento com as ids recém-geradas.

Isso funciona para nosso exemplo de introdução, mas você pode exigir código adicional para outros cenários. Os controles do WPF geram a interface do usuário lendo os campos e propriedades das suas entidades. Quando você edita um valor na interface do usuário (interface do usuário), esse valor é passado para sua entidade. Ao alterar o valor de uma propriedade diretamente na sua entidade, por exemplo, ao carregá-la a partir do banco de dados, o WPF não refletirá imediatamente as alterações na interface gráfica do usuário. O mecanismo de renderização deve ser notificado sobre as alterações. O projeto fez isso chamando manualmente Refresh(). Uma maneira fácil de automatizar essa notificação é implementando a interface INotifyPropertyChanged . Os componentes do WPF detectarão automaticamente a interface e se registrarão para eventos de alteração. A entidade é responsável por gerar esses eventos.

Tip

Para saber mais sobre como lidar com as alterações, leia: Como implementar a notificação de alteração de propriedade.

Próximas etapas

Saiba mais sobre como configurar um DbContext.