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:
|
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="< Anterior"></Button> <Button Margin="0,342,12,0" Name="btnProximo" HorizontalAlignment="Right" Width="108" Click="btnProximo_Click" Height="24" VerticalAlignment="Top" Content="Próximo >"></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.ClientesDataTable
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: