Criar um aplicativo de dados simples com o WPF e o Entity Framework 6

Aviso

Se você estiver usando o Visual Studio 2022, deverá usar o Visual Studio 2022 versão 17.3 Versão Prévia 3 ou posterior para este tutorial.

Este passo a passo mostra como criar um aplicativo básico de formulários de dados no Visual Studio. O aplicativo usa SQL Server LocalDB, o banco de dados Northwind, o Entity Framework 6 (não o Entity Framework Core) e Windows Presentation Foundation para .NET Framework (não .NET Core ou .NET 5 ou posterior). Ele mostra como fazer a vinculação de dados básica com uma exibição de detalhes principais e também tem um Navegador de Associação personalizado com botões para Mover próximo, Mover anterior, Mover para o início, Mover para o final, Atualizar e Excluir.

Este artigo se concentra no uso de ferramentas de dados no Visual Studio e não tenta explicar detalhadamente as tecnologias subjacentes. Ele pressupõe que você tenha uma familiaridade básica com XAML, Entity Framework e SQL. Este exemplo também não demonstra a arquitetura MVVM (Model-View-ViewModel), que é padrão para aplicativos WPF. No entanto, você pode copiar esse código para seu próprio aplicativo MVVM com poucas modificações.

O código final desse tutorial pode ser encontrado no GitHub em Exemplos de Tutorial do Visual Studio – EF6.

Instalar e conectar-se ao Northwind

Este exemplo usa o SQL Server Express LocalDB e o banco de dados de exemplo da Northwind. Se o provedor de dados ADO.NET desse produto tiver suporte ao Entity Framework, também deverá funcionar com outros produtos do banco de dados SQL.

  1. Se você não tiver SQL Server Express LocalDB, instale-o por meio do Instalador do Visual Studio. No Instalador do Visual Studio, você pode instalar o SQL Server Express LocalDB como parte da carga de trabalho Armazenamento e processamento de dados ou como um componente individual.

  2. Instale o banco de dados de exemplo da Northwind seguindo estas etapas:

    1. No Visual Studio, abra a janela Pesquisador de Objetos do SQL Server. (O Pesquisador de Objetos do SQL Server é instalado como parte da carga de trabalho Armazenamento e processamento de dados no Instalador do Visual Studio). Expanda o nó do SQL Server. Clique com o botão direito do mouse na instância do LocalDB e selecione Nova Consulta.

      Uma janela do editor de consultas vai se abrir.

    2. Copie o script Transact-SQL da Northwind para sua área de transferência. Esse script T-SQL cria o banco de dados da Northwind do zero e o preenche com alguns dados.

    3. Cole o script T-SQL no editor de consultas e, em seguida, escolha o botão Executar.

      Após um curto período, a consulta termina de ser executada e o banco de dados da Northwind é criado.

  3. Adicione novas conexões para Northwind.

Configurar o projeto

  1. No Visual Studio, crie um novo projeto C# do aplicativo WPF (.NET Framework).

  2. Adicione o pacote NuGet para o Entity Framework 6. No Gerenciador de Soluções, selecione o nó do projeto. No menu principal, escolha Projeto>Gerenciar Pacotes NuGet.

  3. No Gerenciador de Pacotes NuGet, clique no link Procurar. O Entity Framework é provavelmente o principal pacote da lista. Clique em Instalar no painel direito e siga os prompts. A janela Saída informa quando a instalação é concluída.

    Captura de tela do pacote NuGet do Pacote NuGet do Entity Framework.

    Captura de tela mostrando o Pacote NuGet do Entity Framework.

  4. Agora é possível usar o Visual Studio para criar um modelo com base no banco de dados Northwind.

Criar o modelo

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no nó do projeto e escolha Adicionar>Novo Item. No painel esquerdo, no nó C#, escolha Dados e, no painel central, escolha Modelo de Dados de Entidade ADO.NET.

    Captura de tela do novo item do modelo do Entity Framework.

    Captura de tela do novo item do modelo do Entity Framework.

  2. Chame o modelo Northwind_model e escolha Adicionar. O Assistente do Modelo de Dados de Entidade será aberto. Escolha Designer EF no banco de dados e clique em Avançar.

    Captura de tela do modelo EF do banco de dados.

  3. Na próxima tela, escolha a conexão Northwind do LocalDB (por exemplo, (localdb)\MSSQLLocalDB), especifique o banco de dados Northwind e clique em Avançar.

    Se não aparecer uma conexão, escolha Nova Conexão e, na caixa de diálogo Selecionar Fonte de Dados, escolha Microsoft SQL Server, escolha Continuar e, na caixa de diálogo Propriedades da Conexão, insira (localdb)\MSSQLLocalDB e em Selecionar ou insira um nome de banco de dados, escolha Northwind e pressione OK.

  4. Se solicitado, escolha a versão do Entity Framework que você está usando.

    Captura de tela mostrando as opções de versão.

  5. Na próxima página do assistente, escolha as tabelas, procedimentos armazenados e outros objetos de banco de dados para incluir no modelo do Entity Framework. Expanda o nó dbo no modo de exibição de árvore e escolha Clientes, Pedidos e Detalhes do Pedido. Deixe os padrões marcados e clique em Concluir.

    Captura de tela da escolha de objetos de banco de dados para o modelo.

  6. O assistente gera as classes C# que representam o modelo do Entity Framework. As classes são classes C# antigas simples e são o que associamos à interface do usuário do WPF. O arquivo .edmx descreve os relacionamentos e outros metadados que associam as classes a objetos no banco de dados. Os arquivos .tt são modelos T4 que geram o código que opera no modelo e salva as alterações no banco de dados. É possível ver todos esses arquivos em Gerenciador de Soluções no nó Northwind_model:

    Captura de tela mostrando arquivos de modelo do Entity Framework do Gerenciador de Soluções.

    Captura de tela mostrando arquivos de modelo do Entity Framework do Gerenciador de Soluções

    A superfície do designer do arquivo .edmx permite modificar algumas propriedades e relacionamentos no modelo. Não vamos usar o designer nessas instruções.

  7. Os arquivos .tt são de uso geral e você precisa ajustar um deles para trabalhar com a vinculação de dados do WPF, o que requer ObservableCollections. Em Gerenciador de Soluções, expanda o nó Northwind_model até encontrar Northwind_model.tt. (Verifique se você não está no arquivo .Context.tt, que está diretamente abaixo do arquivo.edmx).

  8. Pressione F5 ou Ctrl+F5 para criar e executar o projeto. Quando o aplicativo é executado pela primeira vez, as classes de modelo ficam visíveis para o assistente de fontes de dados.

Agora você está pronto para conectar esse modelo à página XAML para que possa exibir, navegar e modificar os dados.

Associar o modelo à página XAML

É possível escrever seu próprio código de vinculação de dados, mas é muito mais fácil permitir que o Visual Studio execute por você.

  1. No menu principal, selecione Projeto>Adicionar nova fonte de dados para abrir o Assistente de Configuração da Fonte de Dados. Selecione Objeto porque você associa às classes de modelo, não ao banco de dados:

    Captura de tela do Assistente de Configuração da Fonte de Dados com a Origem do Objeto.

  2. Expanda o nó do seu projeto e selecione Cliente. (As fontes para pedidos são geradas automaticamente a partir da propriedade de Navegação de pedidos no Cliente.)

    Captura de tela mostrando a adição de classes de entidade como fontes de dados.

    Captura de tela mostrando a adição de classes de entidade como fontes de dados.

  3. Clique em Concluir.

  4. Navegue até MainWindow.xaml no Modo de Exibição de Código. Estamos mantendo o XAML simples para fins deste exemplo. Altere o título de MainWindow para algo mais descritivo e aumente sua Altura e Largura para 600 x 800 agora. Sempre altere depois. Agora, adicione essas três definições de linha à grade principal, uma linha para os botões de navegação, uma para os detalhes do cliente e outra para a grade que mostra seus pedidos:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
  5. Agora abra MainWindow.xaml para exibi-lo no designer. Assim, faz a janela Fontes de Dados aparecer como uma opção na margem da janela do Visual Studio ao lado da Caixa de Ferramentas. Clique na guia para abrir a janela ou pressione Shift+Alt+D ou escolha Exibir>Outras Windows>Fonte de dados. Vamos exibir cada propriedade na classe Clientes em sua própria caixa de texto individual. Primeiro, clique na seta na caixa de combinação Clientes e escolha Detalhes. Em seguida, arraste o nó para a parte intermediária da superfície de design para que o designer saiba que você deseja que vá para a linha do meio. Se você perder o nó, poderá especificar a linha manualmente depois no XAML (Grid.Row="1"). Por padrão, os controles são colocados verticalmente em um elemento de grade, mas neste ponto, é possível organizá-los como desejar no formulário. Por exemplo, é possível fazer sentido colocar a caixa de texto Nome na parte superior, acima do endereço. O aplicativo de exemplo deste artigo reordena os campos e os reorganiza em duas colunas.

    Captura de tela mostrando a associação da fonte de dados clientes a controles individuais.

    Captura de tela mostrando a associação da fonte de dados Clientes a controles individuais.

    No modo de exibição XAML, é possível visualizar um novo elemento Grid na linha 1 (a linha intermediária) da grade pai. A grade pai tem um atributo DataContext que se refere a um CollectionViewSource que foi adicionado ao elemento Windows.Resources. Dado esse contexto de dados, quando a primeira caixa de texto se associa a Endereço, esse nome é mapeado para a propriedade Address no objeto atual Customer no CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  6. Quando um cliente estiver visível na metade superior da janela, você deve ver seus pedidos na metade inferior. Você mostra os pedidos em um único controle de exibição de grade. Para que a vinculação de dados de detalhes mestre funcione conforme o esperado, é importante que você associe à propriedade Pedidos na classe Clientes, não ao nó Pedidos separado. Arraste a propriedade Pedidos da classe Clientes para a metade inferior do formulário, para que o designer a coloque na linha 2:

    Captura de tela mostrando as classes Pedidos arrastadas e descartadas como uma grade.

    Captura de tela mostrando as classes Pedidos arrastadas e descartadas como uma grade.

  7. O Visual Studio gerou todo o código de associação que conecta os controles de interface do usuário a eventos no modelo. Tudo o que você precisa fazer, para ver alguns dados, é escrever algum código para preencher o modelo. Primeiro, navegue até MainWindow.xaml.cs e adicione um membro de dados à classe MainWindow para o contexto de dados. Esse objeto, que foi gerado para você, atua como um controle que rastreia alterações e eventos no modelo. Você também adicionará membros de dados CollectionViewSource para clientes e pedidos e a lógica de inicialização do construtor associada ao construtor MainWindow()existente. A parte superior da classe deve ter esta aparência:

    public partial class MainWindow : Window
    {
        NorthwindEntities context = new NorthwindEntities();
        CollectionViewSource custViewSource;
        CollectionViewSource ordViewSource;
    
        public MainWindow()
        {
            InitializeComponent();
            custViewSource = ((CollectionViewSource)(FindResource("customerViewSource")));
            ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource")));
            DataContext = this;
        }
    

    Se ainda não estiver lá, adicione uma diretiva using para System.Data.Entity para colocar o método de extensão Load no escopo:

    using System.Data.Entity;
    

    Agora, role para baixo e localize o manipulador de eventos Window_Loaded. Observe que o Visual Studio adicionou um objeto CollectionViewSource. Isso representa o objeto NorthwindEntities selecionado quando criou o modelo. Já foi adicionado, então não precisa do objeto aqui. Vamos substituir o código no Window_Loaded para que o método agora tenha esta aparência:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Load is an extension method on IQueryable,    
        // defined in the System.Data.Entity namespace.   
        // This method enumerates the results of the query,    
        // similar to ToList but without creating a list.   
        // When used with Linq to Entities, this method    
        // creates entity objects and adds them to the context.   
        context.Customers.Load();
    
        // After the data is loaded, call the DbSet<T>.Local property    
        // to use the DbSet<T> as a binding source.   
        custViewSource.Source = context.Customers.Local;
    }
    
  8. Pressione F5. Veja os detalhes do primeiro cliente que foi recuperado no CollectionViewSource. Você também deve ver seus pedidos na grade de dados. A formatação não é ótima. Por isso, vamos corrigí-la. Você também pode criar uma maneira de exibir os outros registros e fazer operações de criar, ler, atualizar e excluir (CRUD) básicas.

Ajustar o design da página e adicionar grades para novos clientes e pedidos

A disposição padrão produzida pelo Visual Studio não é ideal para seu aplicativo. Por isso, forneceremos o XAML final aqui para copiar seu código. Você também precisa de alguns "formulários" (que na verdade são Grades) para permitir que o usuário adicione um novo cliente ou pedido. Para poder adicionar um novo cliente e pedido, é necessário um conjunto separado de caixas de texto que não estejam associadas a dados de CollectionViewSource. Você controlará a grade do usuário que visualiza a qualquer momento definindo a propriedade Visible nos métodos do manipulador. Por fim, você adiciona um botão Excluir a cada linha na grade Pedidos para permitir que o usuário exclua um pedido individual.

Primeiro, adicione esses estilos ao elemento Windows.Resources no MainWindow.xaml:

<Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}">
    <Setter Property="HorizontalAlignment" Value="Left"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="Margin" Value="3"/>
    <Setter Property="Height" Value="23"/>
</Style>
<Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}">
    <Setter Property="HorizontalAlignment" Value="Right"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="Margin" Value="3"/>
    <Setter Property="Height" Value="26"/>
    <Setter Property="Width" Value="120"/>
</Style>

Em seguida, substitua toda a Grade externa por esta marcação:

<Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="auto"/>
         <RowDefinition Height="auto"/>
         <RowDefinition Height="*"/>
     </Grid.RowDefinitions>
     <Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
         <TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
         <TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/>
         <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                  Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                  Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                  Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                  Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" MinWidth="233"/>
             <ColumnDefinition Width="Auto" MinWidth="397"/>
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Label Content="New Order Form" FontWeight="Bold"/>
         <Label Content="Employee ID:"  Grid.Row="1" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                  Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Order Date:"  Grid.Row="2" Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_orderDatePicker" Grid.Row="2"  HorizontalAlignment="Right" Width="120"
                 SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Shipped Date:"  Grid.Row="4"  Style="{StaticResource Label}"/>
         <DatePicker x:Name="add_shippedDatePicker"  Grid.Row="4"  HorizontalAlignment="Right" Width="120"
                 SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
         <Label Content="Ship Via:"  Grid.Row="5" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_ShipViaTextBox"  Grid.Row="5" Style="{StaticResource CustTextBox}"
                  Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
         <Label Content="Freight"  Grid.Row="6" Style="{StaticResource Label}"/>
         <TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}"
                  Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
     </Grid>
     <DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected">
         <DataGrid.Columns>
             <DataGridTemplateColumn>
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/>
             <DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader">
                 <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                         <DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                     </DataTemplate>
                 </DataGridTemplateColumn.CellTemplate>
             </DataGridTemplateColumn>
             <DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/>
             <DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/>
         </DataGrid.Columns>
     </DataGrid>
 </Grid>

Adicionar botões para navegar, adicionar, atualizar e excluir

Em aplicativos do Windows Forms, você obtém um objeto BindingNavigator com botões para navegar por linhas em um banco de dados e fazer operações CRUD básicas. O WPF não fornece um BindingNavigator, mas é fácil o suficiente para criação. Faça isso com botões dentro de um StackPanel horizontal e associe os botões a Comandos associados a métodos no code-behind.

Há quatro partes na lógica de comando: (1) os comandos, (2) as associações, (3) os botões e (4) os manipuladores de comando no code-behind.

Adicionar comandos, associações e botões em XAML

  1. Primeiro, adicione os comandos no arquivo MainWindow.xaml dentro do elemento Windows.Resources:

    <RoutedUICommand x:Key="FirstCommand" Text="First"/>
    <RoutedUICommand x:Key="LastCommand" Text="Last"/>
    <RoutedUICommand x:Key="NextCommand" Text="Next"/>
    <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/>
    <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/>
    <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/>
    <RoutedUICommand x:Key="UpdateCommand" Text="Update"/>
    <RoutedUICommand x:Key="AddCommand" Text="Add"/>
    <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
    
  2. Um CommandBinding mapeia um evento RoutedUICommand para um método no code-behind. Adicione esse elemento CommandBindings após a marca de fechamento Windows.Resources:

    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/>
        <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/>
        <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/>
        <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/>
        <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/>
        <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/>
        <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/>
    </Window.CommandBindings>
    
  3. Agora, adicione o StackPanel com os botões navegar, adicionar, excluir e atualizar. Primeiro, adicione esse estilo a Windows.Resources:

    <Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="FontFamily" Value="Segoe UI Symbol"/>
        <Setter Property="Margin" Value="2,2,2,0"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="auto"/>
    </Style>
    

    Em segundo lugar, cole esse código logo após o RowDefinitions para o elemento externo Grid em direção à parte superior da página XAML:

    <StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin">
        <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/>
        <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/>
        <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
        <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/>
        <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
        <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
    </StackPanel>
    

Adicionar manipuladores de comando à classe MainWindow

O code-behind é mínimo, exceto para os métodos adicionar e excluir. A navegação é executada pelos métodos na propriedade Exibição do CollectionViewSource. O DeleteOrderCommandHandler mostra como executar uma exclusão em cascata em um pedido. Primeiro, precisamos excluir os Order_Details associados a ele. O UpdateCommandHandler adiciona um novo cliente ou pedido à coleção ou apenas atualiza um cliente ou pedido existente com as alterações feitas pelo usuário nas caixas de texto.

Adicione esses métodos de manipulador à classe MainWindow em MainWindow.xaml.cs. Se o CollectionViewSource para a tabela de Clientes tiver um nome diferente, é preciso ajustar o nome em cada um desses métodos:

private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToLast();
}

private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToPrevious();
}

private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToNext();
}

private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    custViewSource.View.MoveCurrentToFirst();
}

private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    // If existing window is visible, delete the customer and all their orders.  
    // In a real application, you should add warnings and allow the user to cancel the operation.  
    var cur = custViewSource.View.CurrentItem as Customer;

    var cust = (from c in context.Customers
                where c.CustomerID == cur.CustomerID
                select c).FirstOrDefault();

    if (cust != null)
    {
        foreach (var ord in cust.Orders.ToList())
        {
            Delete_Order(ord);
        }
        context.Customers.Remove(cust);
    }
    context.SaveChanges();
    custViewSource.View.Refresh();
}

// Commit changes from the new customer form, the new order form,  
// or edits made to the existing customer form.  
private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    if (newCustomerGrid.IsVisible)
    {
        // Create a new object because the old one  
        // is being tracked by EF now.  
        Customer newCustomer = new Customer
        {
            Address = add_addressTextBox.Text,
            City = add_cityTextBox.Text,
            CompanyName = add_companyNameTextBox.Text,
            ContactName = add_contactNameTextBox.Text,
            ContactTitle = add_contactTitleTextBox.Text,
            Country = add_countryTextBox.Text,
            CustomerID = add_customerIDTextBox.Text,
            Fax = add_faxTextBox.Text,
            Phone = add_phoneTextBox.Text,
            PostalCode = add_postalCodeTextBox.Text,
            Region = add_regionTextBox.Text
        };

        // Perform very basic validation  
        if (newCustomer.CustomerID.Length == 5)
        {
            // Insert the new customer at correct position:  
            int len = context.Customers.Local.Count();
            int pos = len;
            for (int i = 0; i < len; ++i)
            {
                if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0)
                {
                    pos = i;
                    break;
                }
            }
            context.Customers.Local.Insert(pos, newCustomer);
            custViewSource.View.Refresh();
            custViewSource.View.MoveCurrentTo(newCustomer);
        }
        else
        {
            MessageBox.Show("CustomerID must have 5 characters.");
        }

        newCustomerGrid.Visibility = Visibility.Collapsed;
        existingCustomerGrid.Visibility = Visibility.Visible;
    }
    else if (newOrderGrid.IsVisible)
    {
        // Order ID is auto-generated so we don't set it here.  
        // For CustomerID, address, etc we use the values from current customer.  
        // User can modify these in the datagrid after the order is entered.  

        Customer currentCustomer = (Customer)custViewSource.View.CurrentItem;

        Order newOrder = new Order()
        {
            OrderDate = add_orderDatePicker.SelectedDate,
            RequiredDate = add_requiredDatePicker.SelectedDate,
            ShippedDate = add_shippedDatePicker.SelectedDate,
            CustomerID = currentCustomer.CustomerID,
            ShipAddress = currentCustomer.Address,
            ShipCity = currentCustomer.City,
            ShipCountry = currentCustomer.Country,
            ShipName = currentCustomer.CompanyName,
            ShipPostalCode = currentCustomer.PostalCode,
            ShipRegion = currentCustomer.Region
        };

        try
        {
            newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text);
        }
        catch
        {
            MessageBox.Show("EmployeeID must be a valid integer value.");
            return;
        }

        try
        {
            // Exercise for the reader if you are using Northwind:  
            // Add the Northwind Shippers table to the model.
            
            // Acceptable ShipperID values are 1, 2, or 3.  
            if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2"
                || add_ShipViaTextBox.Text == "3")
            {
                newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text);
            }
            else
            {
                MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind.");
                return;
            }
        }
        catch
        {
            MessageBox.Show("Ship Via must be convertible to int");
            return;
        }

        try
        {
            newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text);
        }
        catch
        {
            MessageBox.Show("Freight must be convertible to decimal.");
            return;
        }

        // Add the order into the EF model  
        context.Orders.Add(newOrder);
        ordViewSource.View.Refresh();
    }

    // Save the changes, either for a new customer, a new order  
    // or an edit to an existing customer or order.
    context.SaveChanges();
}

// Sets up the form so that user can enter data. Data is later  
// saved when user clicks Commit.  
private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    existingCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.Visibility = Visibility.Collapsed;
    newCustomerGrid.Visibility = Visibility.Visible;

    // Clear all the text boxes before adding a new customer.  
    foreach (var child in newCustomerGrid.Children)
    {
        var tb = child as TextBox;
        if (tb != null)
        {
            tb.Text = "";
        }
    }
}

private void NewOrder_click(object sender, RoutedEventArgs e)
{
    var cust = custViewSource.View.CurrentItem as Customer;
    if (cust == null)
    {
        MessageBox.Show("No customer selected.");
        return;
    }

    existingCustomerGrid.Visibility = Visibility.Collapsed;
    newCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.UpdateLayout();
    newOrderGrid.Visibility = Visibility.Visible;
}

// Cancels any input into the new customer form  
private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    add_addressTextBox.Text = "";
    add_cityTextBox.Text = "";
    add_companyNameTextBox.Text = "";
    add_contactNameTextBox.Text = "";
    add_contactTitleTextBox.Text = "";
    add_countryTextBox.Text = "";
    add_customerIDTextBox.Text = "";
    add_faxTextBox.Text = "";
    add_phoneTextBox.Text = "";
    add_postalCodeTextBox.Text = "";
    add_regionTextBox.Text = "";

    existingCustomerGrid.Visibility = Visibility.Visible;
    newCustomerGrid.Visibility = Visibility.Collapsed;
    newOrderGrid.Visibility = Visibility.Collapsed;
}

private void Delete_Order(Order order)
{
    // Find the order in the EF model.  
    var ord = (from o in context.Orders.Local
               where o.OrderID == order.OrderID
               select o).FirstOrDefault();

    // Delete all the order_details that have  
    // this Order as a foreign key  
    foreach (var detail in ord.Order_Details.ToList())
    {
        context.Order_Details.Remove(detail);
    }

    // Now it's safe to delete the order.  
    context.Orders.Remove(ord);
    context.SaveChanges();

    // Update the data grid.  
    ordViewSource.View.Refresh();
}

private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    // Get the Order in the row in which the Delete button was clicked.  
    Order obj = e.Parameter as Order;
    Delete_Order(obj);
}

Executar o aplicativo

Para iniciar a depuração, pressione F5. Você deve ver os dados do cliente e do pedido preenchidos na grade, e os botões de navegação devem funcionar conforme o esperado. Clique em Confirmar para adicionar um novo cliente ou pedido ao modelo depois de inserir os dados. Clique em Cancelar para sair de um novo cliente ou novo formulário de pedido sem salvar os dados. Você pode fazer edições para clientes e pedidos existentes diretamente nas caixas de texto e essas alterações são gravadas no modelo automaticamente.