WPF - Cadastro de Clientes com CRUD básico (VB.NET)


Eu tenho dado bastante destaque ao WPF publicando diversos artigos sobre essa tecnologia. Faço isso primeiro porque eu gosto do WPF e porque ele representa uma evolução frente ao já cansado Windows Forms.

Como o Windows Presentation Foundation (WPF) é uma novidade sei que existem muitas dúvidas em como usar os seus recursos, principalmente em aplicações que realizam o acesso e precisam fazer a manutenção de um banco de dados relacional.

Para contribuir com material sobre o assunto neste artigo eu mostro como criar uma aplicação WPF com acesso a dados que realiza as operações CRUD - Create, read, update e delete.

Aplicação WPF - Cadastro de Clientes

Abra o Visual Basic 2010 Express Edition e no menu File clique em New Project e selecione o template WPF Application informando o nome CadastroClientes e clique no botão Ok;

Na figura abaixo vemos a nossa aplicação WPF em ação exibindo os dados dos clientes cadastrados na tabela Clientes de um banco de dados Cadastro.mdf do SQL Server 2005.

A aplicação exemplo realiza a manutenção dos dados de clientes usando uma única janela onde a esquerda temos exibidos em um controle ListBox os nomes dos clientes.
Quando um cliente é selecionado os detalhes são exibidos em controles TextBox no lado direito da janela.

Os controles Button que permitem realizar a navegação pelos registros e as operações de edição, inclusão e exclusão de registros.

Controles usados no arquivo MainWindow.xaml:
  • 1 ListBox - clientesListBox
  • 11 TextBox - TextBox1,...,TextBox11
  • 6 Buttons - btnAnterior, btnSair, btnProximo, btnSalvar, btnExcluir
    e btnNovo;

O código XAML do arquivo MainWindow.xaml da aplicação é dado abaixo:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="Cadastro de Clientes" Height="455" Width="578">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="ClientesListaTemplate">
                <Grid ShowGridLines="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="120" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="16" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="{Binding nome}" Grid.Column="0" Grid.Row="0" />
                </Grid>
            </DataTemplate>
        </Grid.Resources>
        <Grid Name="grid1" Margin="193,12,0,0" Height="314" VerticalAlignment="Top">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="121*" />
                <ColumnDefinition Width="239*" />
            </Grid.ColumnDefinitions>
       <!-- Define as Labels e os TextBox -->
            <Label Height="28" Name="label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Código :"></Label>
            <TextBox Text="{Binding id,Mode=OneWay}" Grid.Column="1" Height="23" Name="textBox1" VerticalAlignment="Top" Margin="0,0,8,0" Background="WhiteSmoke" />
            <Label Margin="0,26,0,260" Name="label2" HorizontalAlignment="Left" Width="93" Content="Nome :"></Label>
            <TextBox x:Name="textBox2" Grid.Row="0" Grid.Column="1" Text="{Binding UpdateSourceTrigger=LostFocus, Path=nome}" 
                     Margin="0,28,8,0" Height="23" VerticalAlignment="Top" />
            <TextBox Text="{Binding nascimento,StringFormat=\{0:dd/MM/yyyy\}}" Grid.Column="1" Margin="0,56,8,0" Name="textBox3" Height="23" VerticalAlignment="Top" />
            <Label Name="label3" Margin="0,54,0,0" Height="24" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Nascimento:"></Label>
            <Label Margin="0,82,0,0" Name="label4" Height="24" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Endereço :"></Label>
            <TextBox Text="{Binding endereco}" Grid.Column="1" Margin="0,84,8,0" Name="textBox4" Height="23" VerticalAlignment="Top" />
            <Label Height="24" Margin="0,110,0,0" Name="label5" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Email :"></Label>
            <TextBox Text="{Binding email}" Grid.Column="1" Height="23" Margin="0,112,8,0" Name="textBox5" VerticalAlignment="Top" />
            <Label Height="24" Margin="0,138,0,0" Name="label6" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Cep :"></Label>
            <TextBox Text="{Binding cep}" Grid.Column="1" Height="23" Margin="0,140,8,0" Name="textBox6" VerticalAlignment="Top" />
            <Label Height="24" Margin="0,166,0,0" Name="label7" VerticalAlignment="Top" HorizontalAlignment="Left" Width="93" Content="Cidade :"></Label>
            <TextBox Text="{Binding cidade}" Height="23" Margin="0,168,8,0" Name="textBox7" VerticalAlignment="Top" Grid.Column="1" />
            <Label Height="24" Margin="0,194,0,0" Name="label8" VerticalAlignment="Top" HorizontalAlignment="Left" Width="90" Content="Celular :"></Label>
            <TextBox Text="{Binding celular}" Height="23" Margin="0,196,8,0" Name="textBox8" VerticalAlignment="Top" Grid.Column="1" />
            <Label Height="24" Margin="0,222,0,0" Name="label9" VerticalAlignment="Top" HorizontalAlignment="Left" Width="90" Content="Contato :"></Label>
            <TextBox Text="{Binding telefone}" Height="23" Margin="0,224,8,0" Name="textBox9" VerticalAlignment="Top" Grid.Column="1" />
            <Label Height="24" Margin="0,250,0,0" Name="label10" VerticalAlignment="Top" HorizontalAlignment="Left" Width="90">E-mail:</Label>
            <TextBox Text="{Binding contato}" Height="23" Margin="0,252,8,0" Name="textBox10" VerticalAlignment="Top" Grid.Column="1" />
            <Label Height="24" Margin="0,278,0,0" Name="label11" VerticalAlignment="Top" HorizontalAlignment="Left" Width="90" Content="Obs:"></Label>
            <TextBox Text="{Binding obs}" Height="23" Margin="0,280,8,0" Name="textBox11" VerticalAlignment="Top" Grid.Column="1" />
        </Grid>
        <!-- Define os Anterior e Proximo e os eventos-->
        <Button Margin="193,342,0,0" Name="btnAnterior" Click="btnAnterior_Click" Height="24" VerticalAlignment="Top" HorizontalAlignment="Left" Width="108" 
Content="&lt; Anterior"></Button>
        <Button Margin="0,342,12,0" Name="btnProximo" HorizontalAlignment="Right" Width="108" Click="btnProximo_Click" Height="24" VerticalAlignment="Top" 
Content="Próximo &gt;"></Button>
        <!-- Define o ListBox e vincula a propriedade DataContext-->
        <ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource ClientesListaTemplate}" 
Name="clientesListBox" HorizontalAlignment="Left" Width="180" Margin="9,12,0,10" ForceCursor="True" Foreground="Yellow" FontSize="14" DataContext="{Binding}">
            <ListBox.Background>
                <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                    <GradientStop Color="Black" Offset="0" />
                    <GradientStop Color="#00B3EBB7" Offset="1" />
                </LinearGradientBrush>
            </ListBox.Background>
        </ListBox>
        <!-- Define os botões Salvar,Novo e Excluir e os eventos-->
        <Button Height="23" Margin="193,382,0,0" Name="btnSalvar" VerticalAlignment="Top" Click="btnSalvar_Click" HorizontalAlignment="Left" Width="108" 
Content="Salvar"></Button>
        <Button Height="23" HorizontalAlignment="Left" Margin="314,382,0,0" Name="btnNovo" VerticalAlignment="Top" Width="108" Click="btnNovo_Click" 
Content="Novo"></Button>
        <Button Height="23" HorizontalAlignment="Left" Margin="437,382,0,0" Name="btnExcluir" VerticalAlignment="Top" Width="108" Click="btnExcluir_Click" 
Content="Excluir"></Button>
        <Button Content="Encerrar" Height="27" HorizontalAlignment="Left" Margin="315,341,0,0" Name="btnSair" VerticalAlignment="Top" Width="107" />
        <Grid.Background>
            <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                <GradientStop Color="Transparent" Offset="0" />
                <GradientStop Color="Black" Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>
</Window>

Observe que estamos usando o DataBinding nos controles TextBox para fazer a vinculação com os dados. Ex: Text="{Binding endereco}"

Estamos definindo também a vinculação dos dados no controle ListBox através da declaração :

ItemsSource="{Binding}" ItemTemplate="{StaticResource ClientesListaTemplate}"

Temos também a definição de um Resource com um DataTemplate identificado por ClientesListaTemplate:

<Grid.Resources>
<DataTemplate x:Key="ClientesListaTemplate">
   <Grid ShowGridLines="True">
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="120" />
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
<RowDefinition Height="16" />
</Grid.RowDefinitions>
   <TextBlock Text="{Binding nome}" Grid.Column="0" Grid.Row="0" />
</Grid>
</DataTemplate>
</Grid.Resources>

O controle ListBox é um controle template e por isso podemos definir um modelo para cada linha, e então repetir omodelo de controle para cada item de dados associado a ele. O elemento XAML DataTemplate inicia a definição de um template chamado ClientesListTemplate onde em cada linha um controle TextBlock é usado para exibir o nome do cliente.

Obs: Para exibir as informações formatadas, um controle de grade pode ser usada dentro da linha de caixa de listagem. Além de declarar o modelo de dados, o controle ListBox deve estar ciente do modelo. Ex: Text="{Binding nascimento,StringFormat=\{0:dd/MM/yyyy\}}"

O banco de dados Cadastro.mdf possui a tabela Clientes a partir da qual foi criado um DataSource com o nome CadastroDataSet.xsd conforme mostra a figura a seguir:

Para isso clique no menu Data e selecione Add New Data Source e selecione o item DataBase e clique em Next;

A seguir selecione DataSet e clique em Next> e selecione a conexão com o banco de dados Cadastro.mdf:

Em seguida selecione a tabela Clientes e aceite o nome do DataSet clicando no botão Finish;

Com o DataSource criado estamos prontos para fazer a aplicação funcionar definindo o código no arquivo MainWindow.xaml.vb.

Vamos iniciar com a declaração dos namespaces usados no projeto:

Imports System.Windows
Imports System.Windows.Data
Imports CadastroClientes.CadastroDataSetTableAdapters
Imports System.Data
Imports System.Data.SqlClient

A seguir vamos declarar as variáveis para a CollectionView e para o DataSet:

Private _dataView As CollectionView
Private clientes As CadastroDataSet.ClientesDataTabl
e

Definimos a seguir uma propriedade somente leitura de onde obtemos uma CollectionView a partir do DataContext:

Friend ReadOnly Property DataView() As CollectionView
        Get
            If _dataView Is Nothing Then
                _dataView = DirectCast(CollectionViewSource.GetDefaultView(Me.DataContext), CollectionView)
            End If
            Return _dataView
        End Get
    End Property

A classe CollectionView representa uma visão para agrupamento, ordenação, filtragem e navegação de uma coleção de dados.

Para criar uma visão para uma coleção que implementa IEnumeralbe, crie um objeto CollectionViewSource, inclua na sua coleção a propriedade Source e obtenha a visão da coleção através da propriedade View.

Você pode pensar na CollectionView como uma camada na parte superior de uma vinculação de origem que permite que você navegue e exiba a coleção com base na classificação, filtro e agrupamento de consultas, tudo sem precisar manipular a coleção de origem subjacentes. (Se a coleção de origem implementa a interface INotifyCollectionChanged, as alterações que disparam o evento CollectionChanged são propagadas para os visões.) 

Nas aplicações WPF todas as coleções tem uma visão de coleção padrão associada. Em vez trabalhar diretamente com a coleção, o mecanismo de ligação sempre acessa a coleção através de exibição associada. Para obter o modo de exibição padrão use o método CollectionViewSource.GetDefaultView.

No evento Loaded da janela declaramos o código que define o DataContext da aplicação:

  Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        Dim clientesTableAdapter As New ClientesTableAdapter()
        clientes = clientesTableAdapter.GetData()
        Me.DataContext = clientes
    End Sub

Para realizar a navegação pelos registros usamos os controles Button e no evento Click dos mesmos usamos as propriedades MoveCurrentToPrevious e MoveCurrentToNext da CollectionView definida no inicio da aplicação.

O código dos botões Anterior e Proximo é mostrado abaixo:

   Private Sub btnAnterior_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        DataView.MoveCurrentToPrevious()
    End Sub

    Private Sub btnProximo_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        DataView.MoveCurrentToNext()
    End Sub

O código do botão Salvar persiste os dados no banco de dados.

Quando efetuamos a vinculação dos dados com os controles através do DataContext qualquer modificação dos dados na janela irá atualizar a fonte de dados.

No exemplo estamos usando um DataSet e um DataTable para armazenar os dados, assim quando os dados forem atualizados nos controles WPF eles serão alterados no objeto DataTable. Como o objeto DataTable armazena os dados na memória para atualizar o banco de dados temos que forçar a atualização usando o comando update do TableAdapter.

No código criamos uma instância da classe ClientesTableAdapter e depois chamamos o método Update passando o objeto clientes como parâmetro. O resultado é um valor inteiro que indica quantas linhas foram atualizadas, inseridas ou excluídas.

 Private Sub btnSalvar_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim customersTableAdapter As New ClientesTableAdapter()
        Dim linhas As Integer = customersTableAdapter.Update(clientes)
        MessageBox.Show("Alterações salvas no banco de dados, " & linhas & " linha(s) atualizadas.")
    End Sub

A seguir temos o código do botão Novo que inclui um registro no banco de dados.

A inclusão de um novo registro é feita pela chamada do método AddClientesRow que pode usar como parâmetro um objeto row ou valores para todos os campos na tabela.
No exemplo usamos o código: clientes.AddClientesRow("", "", "", "", "", "", "", "", "", "")

Nesse código incluímos uma nova linha no objeto DataTable. Neste ponto os componentes WPF da interface são atualizados automaticamente.

Private Sub btnNovo_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        clientes.AddClientesRow("", "", "", "", "", "", "", "", "", "")
        clientesListBox.SelectedIndex = clientes.Rows.Count - 1
    End Sub

Quando você for incluir um novo registro no banco de dados vai perceber que no campo ID será apresentado um valor negativo (-1). Isso se deve ao fato de que na tabela clientes possui o seu campo código id definido como do tipo identity sendo assim um campo auto-numeração.

Dessa forma a interface do usuário não será atualizada com o valor real do campo Id. Podemos contornar esse comportamento usando uma instrução "SELECT @@IDENTITY" que quando executada após a instrução INSERT retorna o valor do campo Id recém gerado.

Para isso usamos um evento na classe DataAdapter para executar a instrução SELECT acima e então atualizar o DataTable com o valor retornado, assim a interface irá exibir o valor correto do campo Id e não o valor -1. Lembre-se que os valores auto-numeração estão disponíveis somente após as alterações serem salvas no banco de dados através do método Update do DataDapter.

Vamos fazer isso definindo o evento RowUpdate associando a um manipulador de eventos a ele com o código:

Dim clientesTableAdapter As clientesTableAdapter = New clienetsTableAdapter()
clientesTableAdapter.Adapter.RowUpdated += New OleDbRowUpdatedEventHandler(AddressOf OnRowUpdated)

Em seguida temos que implementar o código no manipulador OnRowUpdated conforme abaixo:

Private Sub OnRowUpdated(sender As Object, e As OleDbRowUpdatedEventArgs)
if e.StatementType = StatementType.Insert Then
	Dim cmdNewID As New OleDbCommand("SELECT @@IDENTITY", e.Command.Connection)
		e.Row("ID") = CInt(cmdNewID.ExecuteScalar())
	End If
End Sub

No código primeiro verificamos se a instrução SQL executada foi uma instrução INSERT e em seguida criamos um novo objto SqlCommand usando o nome da conexão que foi usada para atualizar o banco de dados. Logo após executamos a instrução SELECT @@IDENTITY que retornará um valor inteiro. (O método ExecuteScalar foi usado justamente para esse propósito).

E assim armazenamos o novo número auto-numeração no objeto DataTable, e, quando a atualização estiver completa a interface irá exibir automaticamente o novo valor Id do campo.

No botão Excluir temos o código que exclui um registro do banco de dados.

O usuário seleciona um usuário no ListBox o item selecionado é retornado através da propriedade SelectedItem do controle. Como o objeto DataTable esta associado ao ListBox o objeto selecionado pela propriedade é do tipo DataRowView e esta classe possui a propriedade Row que pode ser convertida diretamente como um objeto CadastroDataSet.ClientesRow.

Private Sub btnExcluir_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim selectedRow As CadastroDataSet.ClientesRow = DirectCast(DirectCast(clientesListBox.SelectedItem, DataRowView).Row, CadastroDataSet.ClientesRow)
        Dim nome As String = selectedRow.nome
        Dim message As String = "Confirma a exclusão de :  " + "==> """ + nome + """?"
        If MessageBox.Show(message, "Deletar Cliente", MessageBoxButton.YesNoCancel, MessageBoxImage.Exclamation) = MessageBoxResult.Yes Then
            selectedRow.Delete()
        End If
    End Sub

No botão Sair o usuário pode encerrar a aplicação:

 Private Sub btnSair_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSair.Click
        If MessageBox.Show("Deseja encerrar a aplicação?", "Encerrar", MessageBoxButton.YesNo, MessageBoxImage.Information) = MessageBoxResult.Yes Then
            Me.Close()
        End If
    End Sub

E dessa forma temos a nossa aplicação WPF Cadastro de Clientes criada e pronta para ser usada.

Ficou faltando a validação de dados, não é mesmo ???

Essa tarefa eu deixo para você implementar, e, para não dizer que eu não ajudei eu já implementei no projeto a classe Cliente.vb cujo código é mostrado abaixo:

Imports System.ComponentModel

Public Class Cliente
    Implements IDataErrorInfo

    Public Property Nome() As String
        Get
            Return _nome
        End Get
        Set(ByVal value As String)
            _nome = value
        End Set
    End Property
    Private _nome As String
    Public Property Endereco() As String
        Get
            Return _endereco
        End Get
        Set(ByVal value As String)
            _endereco = value
        End Set
    End Property
    Private _endereco As String

    Public ReadOnly Property [Error]() As String Implements IDataErrorInfo.[Error]
        Get
            Throw New NotImplementedException()
        End Get
    End Property

    Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements IDataErrorInfo.Item
        Get
            Dim resultado As String = Nothing
            If columnName = "Nome" Then
                If String.IsNullOrEmpty(Nome) Then
                    resultado = "Informe o nome do cliente"
                End If
            End If
            If columnName = "Endereco" Then
                If String.IsNullOrEmpty(Endereco) Then
                    resultado = "Informe o endereço do cliente"
                End If
            End If
            Return resultado
        End Get
    End Property

End Class

Basta você ler o meu artigo : WPF - Validação com IDataErroInfo e implementar a validação para os campos que desejar.(Na classe estou validando apenas o nome e endereço)

Pegue o projeto completo aqui: CadastroClientesWPF.zip

Eu sei é apenas WPF, mas eu gosto...

Referências:

José Carlos Macoratti