9. Componentes de servidor ASP - 3
9.1. Introduction
Continuamos nosso trabalho na interface do usuário, aprofundando as capacidades dos componentes [DataList] e [DataGrid], especialmente no que diz respeito à atualização dos dados que eles exibem
9.2. Gerenciar eventos associados aos dados dos componentes com ligação de dados
9.2.1. O exemplo
Consideremos a seguinte página:
![]() |
A página inclui três componentes associados a uma lista de dados:
- um componente [DataList] denominado [DataList1]
- um componente [DataGrid] denominado [DataGrid1]
- um componente [Repeater] denominado [Repeater1]
A lista de dados associada é a matriz {"zero", "um", "dois", "três"}. A cada um desses dados está associado um grupo de dois botões denominados [Infos1] e [Infos2]. O usuário clica em um dos botões e um texto exibe o nome do botão clicado. O objetivo aqui é mostrar como gerenciar uma lista de botões ou links.
9.2.2. A configuração dos componentes
O componente [DataList1] está configurado da seguinte maneira:
<asp:datalist id="DataList1" ... runat="server">
<SelectedItemStyle ...</SelectedItemStyle>
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ...></HeaderStyle>
</asp:datalist>
Omitimos tudo o que se referia à aparência do [DataList] para nos concentrarmos apenas em seu conteúdo:
- a seção <HeaderTemplate> define o cabeçalho do [DataList] e a seção <FooterTemplate> define seu rodapé.
- a seção <ItemTemplate> é o modelo de exibição utilizado para cada um dos dados da lista de dados associada. Nela encontram-se os seguintes elementos:
- o valor do dado atual da lista de dados associada ao componente: <%# Container.DataItem %>
- dois botões intitulados, respectivamente, [Infos1] e [Infos2]. A classe [Button] possui um atributo [CommandName] que é utilizado aqui. Ele nos permitirá determinar qual botão originou um evento no [DataList]. Para gerenciar o clique nos botões, teremos apenas um único manipulador de eventos, que estará vinculado ao próprio [DataList] e não aos botões. Esse manipulador receberá uma informação indicando em qual linha do [DataList] ocorreu o clique. O atributo [CommandName] nos permitirá saber em qual botão da linha ele ocorreu.
O componente [Repeater1] é configurado de maneira muito semelhante:
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
Simplesmente adicionamos uma seção <SeparatorTemplate> para que os dados sucessivos exibidos pelo componente sejam separados por uma barra horizontal.
Por fim, o componente [DataGrid1] está configurado da seguinte forma:
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<SelectedItemStyle ...></SelectedItemStyle>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ....></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
Nesse caso também, omitimos as informações de estilo (cores, larguras, etc.). Estamos no modo de geração automática de colunas, que é o modo padrão do [DataGrid]. Isso significa que haverá tantas colunas quantas existirem na fonte de dados. Aqui, há uma. Adicionamos outras duas colunas marcadas por <asp:ButtonColumn>. Nelas, definimos informações semelhantes às definidas para os outros dois componentes, bem como o tipo de botão, neste caso [PushButton]. O tipo padrão é [LinkButton], c.a.d. um link. Além disso, os dados serão paginados [AllowPaging=true] com um tamanho de página de dois elementos [PageSize=2].
9.2.3. O código de apresentação da página
O código de apresentação da nossa página de exemplo foi colocado em um arquivo [main.aspx]:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" autoeventwireup="false" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form runat="server">
<P>Gestion d'événements de composants associés à des listes de données</P>
<HR width="100%" SIZE="1">
<table cellSpacing="1" cellPadding="1" bgColor="#ffcc00" border="1">
<tr>
<td ...>DataList</td>
<td ...>DataGrid</td>
<td ...>Repeater</td>
</tr>
<tr>
<td ...>
<asp:datalist id="DataList1" ... runat="server">
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ....></HeaderStyle>
</asp:datalist>
<P></P>
</td>
<td ...>
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
</td>
<td ...>
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
</td>
</tr>
</table>
<P><asp:label id="lblInfo" runat="server"></asp:label></P>
<P></P>
</form>
</body>
</HTML>
No código acima, omitimos o código de formatação (cores, linhas, tamanhos, etc.)
9.2.4. O código de controle da página
O código de controle do aplicativo foi colocado no arquivo [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
' componentes da página
Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Repeater1 As System.Web.UI.WebControls.Repeater
' a fonte de dados
Protected textes() As String = {"zéro", "un", "deux", "trois"}
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
'ligações com a fonte de dados
DataList1.DataSource = textes
DataGrid1.DataSource = textes
Repeater1.DataSource = textes
Page.DataBind()
End If
End Sub
Private Sub DataList1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles DataList1.ItemCommand
' ocorreu um evento em uma das linhas do [datalist]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [DataList]"
End Sub
Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
' ocorreu um evento em uma das linhas do [repeater]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [Repeater]"
End Sub
Private Sub DataGrid1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.ItemCommand
' ocorreu um evento em uma das linhas do [datagrid]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] de la page [" + DataGrid1.CurrentPageIndex.ToString() + "] du composant [DataGrid]"
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' mudança de página
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = textes
.DataBind()
End With
End Sub
End Class
Comentários:
- a fonte de dados [textes] é uma simples tabela de cadeias de caracteres. Ela será vinculada aos três componentes presentes na página
- essa vinculação é feita no procedimento [Page_Load] durante a primeira consulta. Nas consultas seguintes, os três componentes recuperarão seus valores por meio do mecanismo do [VIEW_STATE].
- Os três componentes possuem um manipulador para o evento [ItemCommand]. Esse evento ocorre quando um botão ou um link é clicado em uma das linhas do componente. O manipulador recebe duas informações:
- fonte: a referência do objeto (botão ou link) que originou o evento
- a: informações sobre o evento do tipo [DataListCommandEventArgs], [RepeaterCommandEventArgs] ou [DataGridCommandEventArgs], conforme o caso. O argumento a traz diversas informações. Aqui, duas delas nos interessam:
- a.Item: representa a linha na qual ocorreu o evento, do tipo [DataListItem], [DataGridItem] ou [RepeaterItem]. Seja qual for o tipo exato, o elemento [Item] possui um atributo [ItemIndex] que indica o número da linha [Item] do contêiner ao qual ele pertence. Exibimos aqui esse número de linha
- a.CommandName: é o atributo [CommandName] do botão (Button, LinkButton, ImageButton) que originou o evento. Essa informação, combinada com a anterior, nos permite saber qual botão do contêiner está na origem do evento [ItemCommand]
9.3. Aplicativo — gerenciamento de uma lista de assinaturas
Agora que sabemos como interceptar os eventos que ocorrem dentro de um contêiner de dados, apresentamos um exemplo que mostra como é possível gerenciá-los.
9.3.1. Introdução
A aplicação apresentada simula um sistema de assinaturas de listas de distribuição. Estas são definidas por um objeto [DataTable] com três colunas:
nome | tipo | função |
string | chave primária | |
string | nome do tema da lista | |
string | uma descrição dos assuntos abordados pela lista |
Para não sobrecarregar nosso exemplo, o objeto [DataTable] acima será construído por código de forma arbitrária. Em uma aplicação real, ele provavelmente seria fornecido por um método de uma classe de acesso a dados. A construção da tabela de listas será feita no procedimento [Application_Start] e a tabela resultante será inserida na aplicação. Vamos chamá-la de tabela [dtThèmes]. O usuário se inscreverá em alguns dos temas dessa tabela. A lista de suas inscrições será mantida, mais uma vez, em um objeto [DataTable] chamado [dtAbonnements], cuja estrutura será a seguinte:
nome | tipo | função |
string | chave primária | |
string | nome do tema da lista |
O aplicativo de página única é o seguinte:
![]() |
n.º | nome | tipo | propriedades | função |
DataGrid | listas de discussão disponíveis para inscrição | |||
DataList | lista das inscrições do usuário nas listas anteriores | |||
painel | painel de informações sobre o tema selecionado pelo usuário com um link [Plus d'informations] | |||
Rótulo | faz parte de [panelInfos] | nome do tema | ||
Etiqueta | faz parte de [panelInfos] | descrição do tema | ||
Rótulo | mensagem informativa do aplicativo |
Nosso exemplo tem como objetivo ilustrar o uso dos componentes [DataGrid] e [DataList], especialmente o gerenciamento dos eventos que ocorrem nas linhas desses contêineres de dados. Portanto, o aplicativo não possui um botão de confirmação que gravaria em um banco de dados, por exemplo, as escolhas feitas pelo usuário. No entanto, ele é realista. Estamos próximos de um aplicativo de comércio eletrônico em que um usuário colocaria produtos (assinaturas) em seu carrinho.
9.3.2. Funcionamento
A primeira tela que o usuário vê é a seguinte:
![]() |
O usuário clica nos links [Plus d'informations] para obter informações sobre um tema. Essas informações são exibidas em [panelInfos]:

Ele clica nos links [S'abonner] para se inscrever em um tema. Os temas escolhidos são registrados no componente [dlAbonnements]:

O usuário pode querer se inscrever em uma lista na qual já está inscrito. Uma mensagem o avisa sobre isso:

Por fim, ele pode cancelar a assinatura de um dos temas usando os botões [Retirer] acima. Não é solicitada nenhuma confirmação. Isso não é necessário neste caso, pois o usuário pode se inscrever novamente com facilidade. Veja a tela após o cancelamento da assinatura de [thème1]:

9.3.3. Configuração dos contêineres de dados
O componente [dgThèmes], do tipo [DataGrid], está vinculado a uma fonte do tipo [DataTable]. Sua formatação foi definida por meio do link [Mise en forme automatique] no painel de propriedades do [DataGrid]. A definição de suas propriedades, por sua vez, foi feita por meio do link [Générateur de proprités] no mesmo painel. O código gerado é o seguinte (o código de formatação foi omitido):
<asp:datagrid id="dgThèmes" AutoGenerateColumns="False" AllowPaging="True" PageSize="5"
runat="server">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="thème" HeaderText="Thème"></asp:BoundColumn>
<asp:ButtonColumn Text="Plus d'informations" CommandName="infos"></asp:ButtonColumn>
<asp:ButtonColumn Text="S'abonner" CommandName="abonner"></asp:ButtonColumn>
</Columns>
<PagerStyle HorizontalAlign="Center" ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
Destacam-se os seguintes pontos:
nós mesmos definimos as colunas a serem exibidas na seção <columns>...</columns> | |
para a paginação dos dados | |
define a coluna [thème] (HeaderText) do [DataGrid], que será vinculada à coluna [thème] da fonte de dados (DataField) | |
define duas colunas de botões (ou links). Para diferenciar os dois links de uma mesma linha, utilizar-se-á sua propriedade [CommandName]. |
O componente [DataGrid] não está totalmente configurado. Ele será configurado no código do controlador.
O componente [dlAbonnements], do tipo [DataList], está vinculado a uma fonte do tipo [DataTable]. Sua formatação foi feita por meio do link [Mise en forme automatique] no painel de propriedades do [DataList]. A definição de suas propriedades, por sua vez, foi feita diretamente no código de apresentação. Esse código é o seguinte (o código de formatação foi omitido):
<asp:DataList id="dlAbonnements" ... runat="server" >
<HeaderTemplate>
<div align="center">
Vos abonnements</div>
</HeaderTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<TABLE>
<TR>
<TD><%#Container.DataItem("tema")%></TD>
<TD>
<asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
</TR>
</TABLE>
</ItemTemplate>
<HeaderStyle ...></HeaderStyle>
</asp:DataList>
define o texto do cabeçalho do [DataList] | |
define o elemento atual do [DataList] — aqui foi inserida uma tabela com duas colunas e uma linha. A primeira célula servirá para conter o nome do tema ao qual o usuário deseja se inscrever, e a outra, o botão [Retirer], que permite que ele cancele sua escolha. |
9.3.4. A página de apresentação
O código de apresentação [main.aspx] é o seguinte:
<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<P>Indiquez les thèmes auxquels vous voulez vous abonner :</P>
<HR width="100%" SIZE="1">
<form runat="server">
<table>
<tr>
<td vAlign="top">
<asp:datagrid id="dgThèmes" ... runat="server">
...
</asp:datagrid>
</td>
<td vAlign="top">
<asp:DataList id="dlAbonnements" ... runat="server" GridLines="Horizontal" ShowFooter="False">
....
</asp:DataList>
</td>
<td vAlign="top">
<asp:Panel ID="panelInfo" Runat="server" EnableViewState="False">
<TABLE>
<TR>
<TD bgColor="#99cccc">
<asp:Label id="lblThème" runat="server"></asp:Label></TD>
</TR>
<TR>
<TD bgColor="#ffff99">
<asp:Label id="lblDescription" runat="server"></asp:Label></TD>
</TR>
</TABLE>
</asp:Panel>
</td>
</tr>
</table>
<P>
<asp:Label id="lblInfo" runat="server" EnableViewState="False"></asp:Label></P>
</form>
</body>
</HTML>
9.3.5. Os controladores
A verificação é dividida entre os arquivos [global.asax] e [main.aspx]. O arquivo [global.asax] é o seguinte:
O arquivo associado [global.asax.vb] contém o seguinte código:
Imports System.Web
Imports System.Web.SessionState
Imports System.Data
Imports System
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' inicializando a fonte de dados
Dim thèmes As New DataTable
' colunas
With thèmes.Columns
.Add("id", GetType(System.Int32))
.Add("thème", GetType(System.String))
.Add("description", GetType(System.String))
End With
' a coluna id será a chave primária
thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
' linhas
Dim ligne As DataRow
For i As Integer = 0 To 10
ligne = thèmes.NewRow
ligne.Item("id") = i.ToString
ligne.Item("thème") = "thème" + i.ToString
ligne.Item("description") = "description du thème " + i.ToString
thèmes.Rows.Add(ligne)
Next
' insere-se a fonte de dados no aplicativo
Application("thèmes") = thèmes
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' início da sessão — cria-se uma tabela de assinaturas vazia
Dim dtAbonnements As New DataTable
With dtAbonnements
' as colunas
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' a chave primária
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' a tabela é inserida na sessão
Session.Item("abonnements") = dtAbonnements
End Sub
End Class
O procedimento [Application_Start], executado quando o aplicativo recebe sua primeira solicitação, cria o [DataTable] dos temas aos quais é possível se inscrever. Ele é criado arbitrariamente com código. Vale lembrar a técnica que já abordamos. Cria-se, na seguinte ordem:
- um objeto [DataTable] vazio, sem estrutura e sem dados
- a estrutura da tabela, definindo suas colunas (nome e tipo de dados que contém)
- as linhas da tabela que representam os dados úteis
Adicionamos aqui uma chave primária. A coluna “id” serve como chave primária. Há várias maneiras de expressar isso. Neste caso, utilizamos uma restrição. No SQL, uma restrição é uma regra que os dados de uma linha devem respeitar para que ela possa ser adicionada a uma tabela. Existem todos os tipos de restrições possíveis. A restrição “Primary Key” obriga a coluna à qual é aplicada a ter valores únicos e não vazios. Uma chave primária pode, na verdade, ser constituída por uma expressão que envolva valores de várias colunas. [DataTable].Constraints é a coleção de restrições de uma determinada tabela. Para adicionar uma restrição, utiliza-se o método [DataTable.Constraints.Add]. Esse método possui várias assinaturas. Aqui, utilizamos o método [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)]:
nome da restrição — pode ser qualquer um | |
coluna que será a chave primária — do tipo [DataColumn] | |
deve ser [vrai] para tornar [colonne] uma chave primária. Se for [cléPrimaire=false], teremos apenas a restrição de valores únicos em [colonne] |
Para tornar a coluna chamada “id” a chave primária da tabela [dtAbonnements], escreve-se, portanto:
O procedimento [Session_Start] é executado quando o aplicativo recebe a primeira solicitação de um cliente. Ele serve para criar objetos específicos para cada cliente, que devem persistir ao longo das diferentes solicitações desse cliente. O procedimento constrói a tabela [DataTable] com as assinaturas do cliente. Apenas sua estrutura é criada, já que, inicialmente, essa tabela está vazia. Ela será preenchida à medida que as solicitações forem ocorrendo. Nesse caso também, a coluna “id” serve como chave primária. Utilizou-se uma técnica diferente para declarar essa restrição:
é a matriz das colunas que formam a chave primária — aqui, declaramos uma matriz com um único elemento: a coluna chamada “id” |
Quando a solicitação do cliente chega ao controlador [main.aspx], os dois objetos [DataTable] estão disponíveis na aplicação para a tabela de temas e na sessão para a tabela de assinaturas. O controlador [main.aspx.vb] é o seguinte:
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents dgThèmes As System.Web.UI.WebControls.DataGrid
Protected WithEvents lblThème As System.Web.UI.WebControls.Label
Protected WithEvents lblDescription As System.Web.UI.WebControls.Label
Protected WithEvents dlAbonnements As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents panelInfo As System.Web.UI.WebControls.Panel
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub liaisons()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
End Class
A função principal do procedimento [Page_Load] é:
- recuperar as duas tabelas [dtThèmes] e [dtAbonnements], que se encontram, respectivamente, no aplicativo e na sessão, a fim de disponibilizá-las a todos os métodos da página
- vincular os dados dessas duas fontes aos seus respectivos contêineres. Isso é feito apenas na primeira consulta. Nas consultas seguintes, a vinculação não precisa ser feita sistematicamente e, quando for necessário fazê-la, às vezes é preciso aguardar um evento posterior ao [Page_Load] para realizá-la.
O código de [Page_Load] é o seguinte:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recuperam-se as fontes de dados
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' ligação de dados
If Not IsPostBack Then
liaisons()
End If
' algumas informações são ocultadas
panelInfo.Visible = False
End Sub
Private Sub liaisons()
'vincula-se a fonte de dados ao componente [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' vincula-se a fonte de dados ao componente [datalist]
With dlAbonnements
.DataSource = dtAbonnements
.DataKeyField = "id"
End With
' atribui-se os dados aos componentes
Page.DataBind()
End Sub
No procedimento [liaisons], utilizamos a propriedade [DataKeyField] dos componentes [DataList] e [DataGrid] para definir a coluna da fonte de dados que servirá para identificar de forma exclusiva as linhas dos contêineres. Normalmente, essa coluna é a chave primária da fonte de dados, mas isso não é obrigatório. Basta que a coluna não contenha duplicatas nem valores vazios. Para o contêiner [dgThèmes], é a coluna “id” da fonte [dtThèmes] que servirá como chave primária e, para o contêiner [dlAbonnements], será a coluna “id” da fonte [dtAbonnements]. Não é necessário que a coluna que serve como chave primária do contêiner seja exibida por ele. Neste caso, nenhum dos dois contêineres exibe a coluna da chave primária. A vantagem de um contêiner ter uma chave primária é que ela permite localizar facilmente, na fonte de dados, as informações relacionadas à linha do contêiner na qual ocorreu um evento. De fato, é comum que, a partir da linha de um contêiner na qual ocorreu um evento, seja necessário agir sobre a linha correspondente na fonte de dados a ela vinculada. A chave primária facilita esse trabalho.
A paginação do [DataGrid] é gerenciada de maneira convencional:
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
' mudança de página
dgThèmes.CurrentPageIndex = e.NewPageIndex
' vinculação
liaisons()
End Sub
As ações nos links [Plus d'informations] e [S'abonner] são gerenciadas pelo procedimento [dgThèmes_ItemCommand]:
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
' evento em uma linha do [datagrid]
Dim commande As String = e.CommandName
Select Case commande
Case "infos"
infos(dgThèmes.DataKeys(e.Item.ItemIndex))
Case "abonner"
abonner(dgThèmes.DataKeys(e.Item.ItemIndex))
End Select
' ligação
liaisons()
End Sub
Utiliza-se o fato de que os dois links possuem o atributo [CommandName] para diferenciá-los. Dependendo do valor desse atributo, chama-se a procedimento [infos] ou [abonner], passando, em ambos os casos, a chave “id” associada ao elemento do [DataGrid] onde ocorreu o evento. Com essas informações, o procedimento [info] exibirá as informações do tema escolhido pelo usuário:
Private Sub infos(ByVal id As String)
' informações sobre o tema da chave id
' recupera-se a linha do [datatable] correspondente à chave
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' exibe-se a informação
lblThème.Text = CType(ligne("thème"), String)
lblDescription.Text = CType(ligne("description"), String)
panelInfo.Visible = True
End If
End Sub
Como a tabela [dtThèmes] possui uma chave primária, o método [dtThèmes.Rows.Find("P")] permite localizar a linha com a chave primária P. Se ela for encontrada, obtém-se um objeto [DataRow]. Aqui, precisamos procurar a linha com a chave primária [id], onde [id] é passado como parâmetro. Se a linha for encontrada, as informações [thème] e [description] dessa linha são inseridas no painel de informações, que é então exibido.
O procedimento [abonner(id)] deve adicionar o tema-chave [id] à lista de assinaturas. Seu código é o seguinte:
Private Sub abonner(ByVal id As String)
' assinatura do tema da chave id
' recupera-se a linha de [datatable] correspondente à chave
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' verifica-se se já não se está inscrito
Dim abonnement As DataRow
abonnement = dtAbonnements.Rows.Find(id)
If Not abonnement Is Nothing Then
' é sinalizado o erro
lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
Else
' adiciona-se o tema às assinaturas
abonnement = dtAbonnements.NewRow
abonnement("id") = id
abonnement("thème") = ligne("thème")
dtAbonnements.Rows.Add(abonnement)
' estabelece-se as conexões
liaisons()
End If
End If
End Sub
Na lista de temas [dtThèmes], procura-se primeiro a linha de chave [id]. Se ela for encontrada, verifica-se se esse tema já não está presente na lista de assinaturas para evitar registrá-lo duas vezes. Se for o caso, exibe-se uma mensagem de erro. Caso contrário, adiciona-se uma nova assinatura à tabela [dtAbonnements] e estabelecem-se as ligações dos componentes da lista de dados às respectivas fontes.
Quando o usuário clica em um botão [Retirer], é necessário excluir um elemento da tabela [dtAbonnements]. Isso é feito pelo seguinte procedimento:
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' cancela-se uma assinatura
Dim commande As String = e.CommandName
If commande = "retirer" Then
' removendo a assinatura de [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' vínculos
liaisons()
End If
End Sub
Primeiramente, verifica-se o atributo [CommandName] do elemento que originou o evento. Na verdade, isso é bastante desnecessário, já que o botão [Retirer] é o único controle capaz de gerar um evento no componente [DataList]. Portanto, não há ambiguidade. Para excluir uma linha de um objeto [DataTable], utiliza-se o método [DataList.Remove(DataRow)], que remove da tabela a linha do tipo [DataRow] passada como parâmetro. Essa linha é localizada pelo método [DataList.Find], ao qual foi passada a chave primária da linha procurada. Após a exclusão da linha, realiza-se a vinculação dos dados aos componentes
9.4. Gerenciar um [DataList] paginado
Retomamos o exemplo anterior para paginar o componente [DataList], que representa a lista de assinaturas do usuário. Ao contrário do componente [DataGrid], o componente [DataList] não oferece nenhum recurso para paginação. Veremos que a implementação dessa funcionalidade é complexa, o que nos fará valorizar ainda mais a paginação automática do [DataGrid].
9.4.1. Funcionamento
A única diferença é a paginação do [DataList]. As páginas terão duas assinaturas. Se o usuário tiver cinco assinaturas, teremos três páginas. A primeira será a seguinte:

A segunda página é acessada pelo link [Suivant]:

A terceira página:

Observe-se que os links [Précédent] e [Suivant] só ficam visíveis se houver, respectivamente, uma página anterior e uma página posterior à página atual.
9.4.2. Código de apresentação
Os links [Précédent] e [Suivant] são obtidos adicionando-se uma tag <FooterTemplate> ao [DataList]:
<asp:datalist id="dlAbonnements" runat="server" ...>
....
<FooterTemplate>
<asp:LinkButton id="lnkPrecedent" runat="server" CommandName="precedent">Précédent</asp:LinkButton>
<asp:LinkButton id="lnkSuivant" runat="server" CommandName="suivant">Suivant</asp:LinkButton>
</FooterTemplate>
....
</asp:datalist>
9.4.3. Código de controle
O arquivo associado [global.asax.vb] sofre as seguintes alterações:
...
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' início da sessão — cria-se uma tabela de assinaturas vazia
Dim dtAbonnements As New DataTable
With dtAbonnements
' as colunas
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' a chave primária
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' a tabela é inserida na sessão
Session.Item("abonnements") = dtAbonnements
' a página atual é a página 0
Session.Item("pAC") = 0
' o número de assinaturas nesta página é 0
Session.Item("nbAC") = 0
End Sub
Além da tabela de assinaturas [dtAbonnements], são inseridas na sessão mais duas informações:
do tipo [Integer] — trata-se do número da página atual exibida na última consulta | |
do tipo [Integer] — número de linhas exibidas na página atual anterior |
No início de uma sessão, o número da página atual e o número de linhas dessa página são nulos.
O controlador [main.aspx.vb] evolui da seguinte forma:
....
Public Class main
Inherits System.Web.UI.Page
....
' dados do aplicativo
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub terminer()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
Private Sub changePAC()
...
End Sub
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean)
...
End Sub
End Class
Nele são definidos novos dados relacionados à paginação das assinaturas:
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Algumas dessas informações são armazenadas na sessão e são recuperadas a cada solicitação no procedimento [Page_Load]:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' as fontes de dados são recuperadas
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' e as informações de exibição das assinaturas
pAC = CType(Session("pAC"), Integer)
nbAC = CType(Session("nbAC"), Integer)
' ligação de dados
If Not IsPostBack Then
' exibição de uma lista vazia de assinaturas
terminer()
End If
' ocultando determinadas informações
panelInfo.Visible = False
End Sub
As informações recuperadas [pAC] e [nbAC] são informações sobre a página de assinaturas exibida na consulta anterior:
este é o número da página atual exibida na solicitação anterior | |
número de linhas exibidas nesta página atual |
O método [terminer] estabelece as ligações entre os componentes e suas fontes de dados, da mesma forma que o método [liaisons] fazia na aplicação anterior. A novidade, neste caso, é a ligação do [DataList] à tabela [dtPA], que é a página de assinaturas a ser exibida:
Private Sub terminer()
' vinculação da fonte de dados ao componente [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' vincula-se a página de assinaturas ao componente [datalist], levando em consideração a página atual pAC
changePAC()
' visualiza-se a página p
With dlAbonnements
.DataSource = dtPA
.DataKeyField = "id"
End With
' atribui-se os dados aos componentes
Page.DataBind()
' gerenciamento dos links [précédent] e [suivant] do [datalist]
Dim blprec As Boolean = pAC <> 0
Dim blsuivant As Boolean = pAC <> (dtAbonnements.Rows.Count - 1) \ nbAP
Dim nbLiensTrouvés As Integer = 0
setLiens(dlAbonnements, blprec, blsuivant, nbLiensTrouvés)
' as informações da página atual são salvas na sessão
Session("pAC") = pAC
Session("nbAC") = dtPA.Rows.Count
End Sub
Os seguintes pontos devem ser observados:
- a fonte [dtPA] depende do número da página atual [pAC] a ser exibida. A variável [pAC] é uma variável global da classe, manipulada pelos métodos responsáveis por alterar esse número da página atual. É o método [changePAC] que realiza a construção da tabela [dtPA], que será vinculada ao componente [dlAbonnements].
- O método [setLiens] tem a função de exibir ou ocultar os links [Précédent] e [Suivant], dependendo se a página atual [pAC] exibida é precedida e seguida por uma página. Ela possui quatro parâmetros:
- [dlAbonnements]: o controle [DataList], cuja árvore de controles será explorada para localizar os dois links. De fato, embora esses dois links estejam em um local específico, que é o rodapé do [DataList], não parece haver uma maneira simples de referenciá-los diretamente. De qualquer forma, isso não foi encontrado aqui.
- [blPrecedent]: valor booleano a ser atribuído à propriedade [visible] do link [Precedent] — é verdadeiro se a página atual for diferente de 0
- [blSuivant]: valor booleano a ser atribuído à propriedade [visible] do link [Suivant] — é verdadeiro se a página atual não for a última página da lista de assinaturas
- [nbLiensTrouvés]: um parâmetro de saída que conta o número de links encontrados. Assim que esse número for igual a dois, o método é encerrado.
- As informações [pAC] e [nbAC] são salvas na sessão para a próxima consulta.
O método [changePAC] cria a tabela [dtPA], que será vinculada ao componente [dlAbonnements]. Isso é feito com base no número [pAC] da página atual a ser exibida. A tabela [dtPA] deve exibir determinadas linhas da tabela de assinaturas [dtAbonnements]. Vale lembrar que essa tabela é armazenada na sessão e atualizada (aumentada ou reduzida) à medida que as consultas são realizadas. Começamos definindo o intervalo [premier,dernier] dos números das linhas da tabela [dtAbonnements] que a tabela [dtPA] deve exibir:
Private Sub changePAC()
' define a página pAC como a página atual das assinaturas
' gerenciamento das páginas do [datalist]
Dim nbAbonnements = dtAbonnements.Rows.Count
Dim dernièrePage = (nbAbonnements - 1) \ nbAP
' primeira e última assinatura
If pAC < 0 Then pAC = 0
If pAC > dernièrePage Then pAC = dernièrePage
Dim premier As Integer = pAC * nbAP
Dim dernier As Integer = (pAC + 1) * nbAP - 1
If dernier > nbAbonnements - 1 Then dernier = nbAbonnements - 1
Feito isso, podemos criar a tabela [dtPA]. Primeiramente, define-se sua estrutura [id,thème] e, em seguida, preenche-se a tabela copiando as linhas de [dtAbonnements] cujos números estão no intervalo [premier,dernier], calculado anteriormente.
' criação da tabela de dados dtpa
dtPA = New DataTable
With dtPA
' as colunas
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' a chave primária
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
Dim abonnement As DataRow
For i As Integer = premier To dernier
abonnement = dtPA.NewRow
With abonnement
.Item("id") = dtAbonnements.Rows(i).Item("id")
.Item("thème") = dtAbonnements.Rows(i).Item("thème")
End With
dtPA.Rows.Add(abonnement)
Next
End Sub
Ao final do método [changePAC], a tabela [dtPA] foi criada e pode ser vinculada ao componente [DataList]. Isso é feito no método [terminer]. Nesse mesmo método, utiliza-se o procedimento [setLiens] para definir o status das ligações [Précédent] e [Suivant] do [DataList]. O código desse procedimento é o seguinte:
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
' procurando os links [précédent] e [suivant]
' na árvore de controles do [datalist]
' Encontramos todos os links?
If nbLiensTrouvés = 2 Then Exit Sub
' análise dos controles filhos
Dim c As Control
For Each c In ctl.Controls
' primeiro, trabalhamos em profundidade — os links estão na parte inferior da árvore
setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
' link [Précédent]?
If c.ID = "lnkPrecedent" Then
CType(c, LinkButton).Visible = blPrec
nbLiensTrouvés += 1
End If
' link [Suivant]?
If c.ID = "lnkSuivant" Then
CType(c, LinkButton).Visible = blSuivant
nbLiensTrouvés += 1
End If
Next
End Sub
O procedimento é recursivo. Ele procura, em primeiro lugar, entre os controles filhos do componente [dlAbonnements], os componentes denominados [lnkPrecedent] e [lnkSuivant], que correspondem às identidades (atributo ID) dos dois links de paginação. Ela os procura inicialmente a partir da base da árvore de controles, pois é lá que eles se encontram. Assim que um link é encontrado, o contador [nbLiensTrouvés] é incrementado e a propriedade [visible] do link é preenchida com um valor passado como parâmetro do procedimento. Assim que os dois links forem encontrados, a árvore de controles não é mais explorada e o procedimento recursivo é encerrado.
Mencionamos que o método [changePAC], que define a fonte de dados [dtPA] para o componente [dlAbonnements], trabalhava com o número [pAC] da página atual a ser exibida. Vários procedimentos modificam esse número:
Private Sub abonner(ByVal id As String)
' assinatura do tema com ID de chave
..
' adicionamos o tema às assinaturas
..
' atualização do número da página atual — agora é a última página
pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
' vínculos de dados
terminer()
End If
End Sub
Após a adição de uma assinatura, ela aparece no final da lista de assinaturas. Por isso, o usuário é direcionado para a última página das assinaturas, para que possa ver a adição que foi realizada.
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' remoção de uma assinatura
Dim commande As String = e.CommandName
Select Case commande
Case "retirer"
' removendo a assinatura do [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' é necessário mudar a página atual?
nbAC -= 1
If nbAC = 0 Then pAC -= 1
Case "precedent"
' mudança da página atual
pAC -= 1
Case "suivant"
' mudança da página atual
pAC += 1
End Select
' ligações de dados
terminer()
End Sub
- [nbAC] é o número de linhas exibidas na página atual antes do cancelamento de uma assinatura. Se o novo número de linhas da página for igual a 0, o número da página atual [pAC] é diminuído em uma unidade.
- Ao clicar no link [Precedent], o número da página atual [pAC] é diminuído em uma unidade.
- Ao clicar no link [Suivant], o número da página atual [pAC] é incrementado em uma unidade.
Os demais procedimentos permanecem idênticos aos anteriores.
9.4.4. Conclusão
Mostramos neste exemplo que é possível paginar um componente [DataList]. Essa paginação é complexa e é preferível recorrer à paginação automática do componente [DataGrid] sempre que possível. Este exemplo também nos mostrou como acessar os componentes presentes no rodapé do componente [DataList].
9.5. Classe de acesso a uma base de produtos
Voltamos a nos concentrar no banco de dados ACCESS [produits] já utilizado. Vale lembrar que ele possui uma única tabela chamada [liste], cuja estrutura é a seguinte:
![]() | ![]() |
Vamos criar uma classe de acesso à tabela [liste] que permitirá lê-la e atualizá-la. Também criaremos um cliente de console que utilizará a classe anterior para atualizar a tabela. Em uma segunda etapa, criaremos um cliente web para realizar essa mesma tarefa.
9.5.1. A classe ExceptionProduits
A classe [Exception] possui um construtor que aceita uma mensagem de erro como parâmetro. Aqui, queremos dispor de uma classe de exceção com um construtor que aceite uma lista de mensagens de erro, em vez de uma única mensagem de erro. Essa será a classe [ExceptionProduits], apresentada a seguir:
Public Class ExceptionProduits
Inherits Exception
' mensagens de erro relacionadas à exceção
Private _erreurs As ArrayList
' construtor
Public Sub New(ByVal erreurs As ArrayList)
Me._erreurs = erreurs
End Sub
' propriedade
Public ReadOnly Property erreurs() As ArrayList
Get
Return _erreurs
End Get
End Property
End Class
9.5.2. A estrutura [sProduit]
A estrutura [produit] representará um produto [id, nom, prix]:
' estrutura sProduit
Public Structure sProduit
' os campos
Private _id As Integer
Private _nom As String
Private _prix As Double
' ID da propriedade
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = Value
End Set
End Property
' propriedade nome
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
_nom = Value
End Set
End Property
' propriedade preço
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If IsNothing(Value) OrElse Value < 0 Then Throw New Exception
_prix = Value
End Set
End Property
End Structure
A estrutura aceita apenas dados válidos para os campos [nom] e [prix].
9.5.3. A classe Produtos
A classe [Produits] é a classe que nos permitirá atualizar a tabela [liste] do banco de dados de produtos. Sua estrutura é a seguinte:
Public Class produits
' dados de instância
Public Sub New(ByVal chaineConnexionOLEDB As String)
....
End Sub
Public Function getProduits() As DataTable
....
End Function
Public Sub ajouterProduit(ByVal produit As sProduit)
...
End Sub
Public Sub modifierProduit(ByVal produit As sProduit)
...
End Sub
Public Sub supprimerProduit(ByVal id As Integer)
...
End Sub
End Class
Dados de instância
Os dados compartilhados pelos diferentes métodos da classe são os seguintes:
Private connexion As OleDbConnection
Private Const selectText As String = "select id,nom,prix from liste"
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
Private Const updateText As String = "update liste set nom=?,prix=? where id=?"
Private Const deleteText As String = "delete from liste where id=?"
Private selectCommand As New OleDbCommand
Dim insertCommand As New OleDbCommand
Dim updateCommand As New OleDbCommand
Dim deleteCommand As New OleDbCommand
Dim adaptateur As New OleDbDataAdapter
a conexão com o banco de dados — será aberta para a execução de um comando SQL e fechada imediatamente em seguida | |
consulta SQL [select] recuperando toda a tabela [liste] | |
consulta que permite a inserção de uma linha (nome, preço) na tabela [liste]. Observe-se que não se especifica o campo [id]. De fato, esse campo é autoincrementado pelo SGBD e, portanto, não precisamos especificá-lo. | |
consulta que permite a atualização dos campos (nome, preço) da linha da tabela [liste] com a chave [id] | |
consulta que permite a exclusão da linha da tabela [liste] com a chave [id] | |
objeto [OleDbCommand] executando a consulta [selectText] na conexão [connexion] | |
objeto [OleDbCommand] executando a consulta [updateText] na conexão [connexion] | |
objeto [OleDbCommand] executando a consulta [insertText] na conexão [connexion] | |
objeto [OleDbCommand] executando a consulta [deleteText] na conexão [connexion] | |
objeto que permite recuperar o resultado da execução de [selectCommand] em um objeto [DataSet] |
O construtor
O construtor recebe um único parâmetro, [chaineConnexionOLEDB], que é a string de conexão que designa o banco de dados a ser utilizado. A partir dela, são preparadas as quatro instruções de consulta e atualização da tabela, bem como o adaptador. Trata-se apenas de uma preparação, e nenhuma conexão é estabelecida.
Public Sub New(ByVal chaineConnexionOLEDB As String)
' preparando a conexão
connexion = New OleDbConnection(chaineConnexionOLEDB)
' preparando os comandos de consulta
Dim commandes() As OleDbCommand = {selectCommand, insertCommand, updateCommand, deleteCommand}
Dim textes() As String = {selectText, insertText, updateText, deleteText}
For i As Integer = 0 To commandes.Length - 1
With commandes(i)
.CommandText = textes(i)
.Connection = connexion
End With
Next
' preparando o adaptador de acesso aos dados
adaptateur.SelectCommand = selectCommand
End Sub
O método getProduits
Este método permite obter o conteúdo da tabela [Liste] em um objeto [DataTable]. Seu código é o seguinte:
Public Function getProduits() As DataTable
' a tabela [liste] está sendo inserida em [dataset]
Dim contenu As New DataSet
' cria-se um objeto DataAdapter para ler os dados da fonte OLEDB
Try
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
Catch e As Exception
' pb
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur d'accès à la base de données : {0}", e.Message))
Throw New ExceptionProduits(erreursCommande)
End Try
' retornamos o resultado
Return contenu.Tables(0)
End Function
O trabalho é realizado pelas duas instruções a seguir:
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
O método [FillSchema] define a estrutura (colunas, restrições, relações) do [DataSet] e do contenu com base na estrutura do banco de dados referenciado pelo [adaptateur.Connexion]. Isso nos permite recuperar a estrutura da tabela [liste] e, em especial, sua chave primária. A operação [Fill] a seguir preenche as tabelas [Dataset] e contenu com as linhas da tabela [liste]. Com essa única operação, teríamos obtido os dados e a estrutura, mas não a chave primária. No entanto, ela será útil para atualizar a tabela [liste] na memória. Aqui, assim como nos outros métodos, tratamos os possíveis erros com a ajuda da classe [ExceptionProduits] para obter uma lista (ArrayList) de erros, em vez de um único erro. O método [getProduits] retorna a tabela [liste] na forma de um objeto [DataTable].
O método ajouterProduits
Este método permite adicionar uma linha (id, nome, preço) à tabela [liste]. Essas informações são fornecidas na forma de uma estrutura [sProduit] composta por campos [id, nom, prix]. O código do método é o seguinte:
Public Sub ajouterProduit(ByVal produit As sProduit)
' adiciona-se um produto [nom,prix]
' preparamos os parâmetros da adição
With insertCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
End With
' fazemos a adição
Try
' início da conexão
connexion.Open()
' execução do comando
insertCommand.ExecuteNonQuery()
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' encerramento da conexão
connexion.Close()
End Try
End Sub
Os campos da estrutura [produit] são inseridos nos parâmetros do comando [insertCommand]. Vale lembrar a configuração atual desse comando (consulte o fabricante):
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
insertCommand.Connexion=connexion
O texto do comando SQL [insert] contém parâmetros formais que devem ser substituídos por parâmetros efetivos. Isso é feito com a coleção [Parameters] da classe [OleDbCommand]. Essa coleção contém elementos do tipo [OleDbParameter] que definem os parâmetros efetivos que devem substituir os parâmetros formais ?. Como estes últimos não são nomeados, utiliza-se o índice dos parâmetros efetivos para determinar a qual parâmetro formal corresponde determinado parâmetro efetivo. Aqui, o parâmetro efetivo nº i na coleção [Parameters] substituirá o parâmetro formal ? nº i. Para criar um parâmetro efetivo do tipo [OleDbParameter], utiliza-se aqui o construtor [OleDbParameter (Byval nom as String, Byval valeur as Object)], que define o nome e o valor do parâmetro efetivo. O nome pode ser qualquer um. Além disso, neste caso, ele não será utilizado. Os dois parâmetros da instrução SQL [insert] recebem como valores os dos campos [nom, prix] da estrutura [produit]. Feito isso, a inserção é realizada pela instrução [insertCommand.ExecuteNonQuery].
O método modifierProduits
Este método permite modificar a linha da tabela [liste]. As informações necessárias são fornecidas na estrutura [sProduit], nos campos [id, nom, prix].
Public Sub modifierProduit(ByVal produit As sProduit)
' modificação de um produto [id,nom,prix]
' preparação dos parâmetros da atualização
With updateCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
.Add(New OleDbParameter("id", produit.id))
End With
' realização da modificação
Try
' abertura da conexão
connexion.Open()
' execução do comando
Dim nbLignes As Integer = updateCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", produit.id))
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' encerramento da conexão
connexion.Close()
End Try
End Sub
O código é praticamente idêntico ao do método [ajouterProduits], com a única diferença de que o comando [OleDbCommand] em questão é [updateCommand], em vez de [insertCommand].
O método supprimerProduits
Este método permite excluir a linha da tabela [liste] cuja chave [id] foi passada como parâmetro. O código é o seguinte:
Public Sub supprimerProduit(ByVal id As Integer)
' exclui o produto da chave [id]
' prepara-se os parâmetros para a exclusão
With deleteCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", id))
End With
' realiza a exclusão
Try
' abertura da conexão
connexion.Open()
' execução do comando
Dim nbLignes As Integer = deleteCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", id))
Catch ex As Exception
' problema
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' encerramento da conexão
connexion.Close()
End Try
End Sub
Segue-se o mesmo procedimento utilizado nos métodos anteriores.
9.5.4. Testes da classe [produits]
Um programa de testes [testproduits.vb] do tipo console poderia ser o seguinte:
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Namespace st.istia.univangers.fr
' página de teste
Module testproduits
Dim contenu As DataTable
Sub Main(ByVal arguments() As String)
' exibe o conteúdo de uma tabela de produtos
' a tabela está em um banco de dados ACCESS, cuja página recebe o nome do arquivo
Const syntaxe1 As String = "pg bdACCESS"
' verificação dos parâmetros do programa
If arguments.Length <> 1 Then
' mensagem de erro
Console.Error.WriteLine(syntaxe1)
' fim
Environment.Exit(1)
End If
' preparação da cadeia de conexão
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
' criação de um objeto de produtos
Dim objProduits As produits = New produits(chaineConnexion)
' exibindo todos os produtos
afficheProduits(objProduits)
' inserindo um produto
Dim produit As New sProduit
With produit
.nom = "xxx"
.prix = 1
End With
Try
objProduits.ajouterProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' exibindo todos os produtos
afficheProduits(objProduits)
' recuperando o ID do produto adicionado
produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
' modifica-se o produto adicionado
produit.prix = 200
Try
objProduits.modifierProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' exibe todos os produtos
afficheProduits(objProduits)
' exclui o produto adicionado
Try
objProduits.supprimerProduit(produit.id)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' exibe todos os produtos
afficheProduits(objProduits)
End Sub
Sub afficheProduits(ByRef objProduits As produits)
' recupera-se a tabela de produtos em um datatable
Try
contenu = objProduits.getProduits()
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
Environment.Exit(2)
End Try
Dim lignes As DataRowCollection = contenu.Rows
For i As Integer = 0 To lignes.Count - 1
' linha i da tabela
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
' interrompe o fluxo do console
Console.WriteLine("...")
Console.ReadLine()
End Sub
Sub afficheErreurs(ByRef erreurs As ArrayList)
' exibe os erros no console
If erreurs.Count <> 0 Then
Console.WriteLine("Les erreurs suivantes se sont produites :")
For i As Integer = 0 To erreurs.Count - 1
Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
Next
End If
' interrompe o fluxo do console
Console.WriteLine("...")
Console.ReadLine()
End Sub
End Module
End Namespace
Compilamos os dois arquivos-fonte:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos >vbc /r:produits.dll /r:system.dll /r:system.data.dll /r:system.xml.dll testproduits.vb
dos>dir
07/04/2004 08:40 7 168 produits.dll
04/04/2004 16:38 118 784 produits.mdb
07/04/2004 08:31 6 209 produits.vb
07/04/2004 08:40 5 120 testproduits.exe
03/04/2004 19:02 3 312 testproduits.vb
e então testamos:
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,1
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,200
...
1,produit1,10
2,produit2,20
3,produit3,30
...
Solicita-se ao leitor que compare a saída de tela acima com o código do programa de teste.
9.6. Aplicativo web para atualização da tabela de produtos em cache
9.6.1. Introdução
Estamos desenvolvendo agora um aplicativo web para atualização da tabela de produtos (adição, exclusão, modificação). A tabela atualizada permanecerá na memória em um objeto [DataTable] e será compartilhada por todos os usuários. Gostaríamos de destacar dois pontos:
- o gerenciamento de um objeto [DataTable]
- os problemas relacionados à atualização simultânea de uma tabela por vários usuários.
A arquitetura MVC do aplicativo será a seguinte:
![]() |
9.6.2. Funcionamento e visualizações
A tela inicial do aplicativo é a seguinte:

Essa tela, denominada [formulaire], permite que o usuário defina um critério de filtragem para os produtos e especifique o número de produtos por página que deseja visualizar.
n.º | nome | tipo | função |
LinkButton | exibe a visão [Formulaire], que serve para definir a condição de filtragem | ||
LinkButton | exibe a visão [Produits], que serve para visualizar e atualizar a tabela de produtos (alteração e exclusão) | ||
LinkButton | exibe a tela [Ajout], que serve para adicionar um produto | ||
vueFormulaire | a visão [Formulaire] | ||
TextBox | a condição de filtragem | ||
TextBox | número de produtos por página | ||
RequiredFieldValidator | verifica se há um valor em [txtPages] | ||
RangeValidator | verifica se txtPages está no intervalo [3,10] | ||
botão [submit] que exibe a visualização [produits] filtrada pela condição (5) | |||
Rótulo | Texto informativo em caso de erros |
Por exemplo, se a visualização [formulaire] for preenchida da seguinte forma:

obtém-se o seguinte resultado:
![]() |
n.º | nome | tipo | função |
painel | |||
RadioButton | permite que o usuário defina a ordem de classificação desejada ao clicar no título de uma das colunas [nom], [prix]. Os dois botões fazem parte do grupo [rdTri]. | ||
DataGrid | grade de exibição de uma visualização filtrada da tabela de produtos. O filtro é aquele definido pela visualização [formulaire]. Também temos .AllowPaging=true, .AllowSorting=true | ||
DataGrid | exibe toda a tabela de produtos — permite acompanhar as atualizações | ||
Rótulo | texto informativo, especialmente em caso de erros | ||
DataGrid | exibirá os produtos excluídos na tabela de produtos | ||
DataGrid | exibirá os produtos modificados na tabela de produtos | ||
DataGrid | exibirá os produtos adicionados na tabela de produtos |
Há cinco contêineres de dados nesta página. Todos eles exibem a mesma tabela [dtProduits] por meio de uma visualização [DataView] diferente. Uma visualização representa um subconjunto das linhas da tabela de origem da visualização. Esse subconjunto é criado a partir das propriedades [RowFilter] e [RowStateFilter] da classe [DataView]:
- [RowFilter] permite aplicar um filtro às linhas, como, por exemplo, o [prix>30] acima. Esse tipo de filtragem será utilizado pelo [DataGrid1].
- [RowStateFilter] permite definir um filtro de acordo com o estado da linha da tabela. Esse filtro indica o estado da linha em relação ao estado original que ela possuía quando a visualização da tabela foi criada. Aqui, a tabela [dtProduits] provém de um banco de dados. Inicialmente, todas as suas linhas terão um estado igual a [Original] para indicar que se trata das linhas originais da tabela. Esse estado pode, em seguida, mudar e assumir diferentes valores, dos quais apresentamos alguns a seguir:
- [Added]: a linha foi adicionada — ela não fazia parte da tabela original
- [Deleted]: a linha foi excluída — ela ainda está na tabela, mas está “marcada” como “a ser excluída”
- [Modified]: a linha foi modificada
[RowStateFilter] permite exibir as linhas da tabela com um determinado status:
- (continuação)
- [DataViewRowState.Added]: apenas as linhas adicionadas são exibidas. Elas são exibidas com seus valores atuais.
- [DataViewRowState.ModifiedOriginal]: apenas as linhas modificadas são exibidas. Elas são exibidas com seus valores originais.
- [DataViewRowState.ModifiedCurrent]: apenas as linhas modificadas são exibidas. Elas são exibidas com seus valores atuais.
- [DataViewRowState.Deleted]: apenas as linhas excluídas são exibidas. Elas são exibidas com seus valores originais.
- [DataViewRowState.CurrentRows]: as linhas não excluídas são exibidas. Elas são exibidas com seus valores atuais.
Assim, o filtro [RowStateFilter] terá os seguintes valores:
sem filtro para o estado das linhas | ||
DataViewRowState.CurrentRows | exibe o estado atual da tabela de produtos | |
DataViewRowState.Deleted | exibe as linhas excluídas da tabela de produtos | |
DataViewRowState.ModifiedOriginal | exibe as linhas modificadas da tabela de produtos com seus valores iniciais | |
DataViewRowState.Added | exibe as linhas adicionadas à tabela de produtos inicial |
Os contêineres [DataGrid1-5] nos permitirão acompanhar a atualização da tabela [dtProduits]. O componente [DataGrid1] permite a modificação e a exclusão de um produto. Veremos como a configuração do componente permite essa atualização. Ele também é paginado e classificado. Esses dois pontos já foram abordados em um exemplo anterior.
O link [Ajout] dá acesso a um formulário para adição de produto:
![]() |
n.º | nome | tipo | função |
painel | |||
TextBox | nome do produto | ||
RequiredFieldValidator | verifica se há um valor em [txtNom] | ||
TextBox | preço do produto | ||
RequiredFieldValidator | verifica se há um valor em [txtPrix] | ||
CompareValidator | verifica se preço >= 0 | ||
Botão | botão [submit] para adicionar o produto | ||
Rótulo | Texto informativo sobre o resultado da operação de adição |
O banco de dados [produits] pode estar indisponível ao iniciar o aplicativo. Nesse caso, a visualização [erreurs] é exibida ao usuário:
![]() |
n.º | nome | tipo | função |
painel | |||
Repetidor | lista de erros |
9.6.3. Configuração dos contêineres de dados
Os cinco contêineres [DataGrid] foram configurados em [WebMatrix]. Eles foram formatados (cores e bordas) por meio do link [Configuration automatique] em seu painel de propriedades. O contêiner [DataGrid1] foi configurado por meio do link [Générateur de propriétés] nesse mesmo painel. A classificação foi habilitada (guia [Général]):

As colunas não são geradas automaticamente, ao contrário dos outros quatro contêineres. Elas foram definidas manualmente por meio do assistente:

Primeiramente, criamos duas colunas do tipo [Colonne connexe], denominadas [nom] e [prix]. Elas foram associadas, respectivamente, aos campos [nom] e [prix] da fonte de dados que os contêineres exibirão. Veja, por exemplo, a configuração da coluna [nom]:

A expressão de ordenação é a expressão que deverá ser colocada após a cláusula [order by] da instrução SQL [select], que será executada quando ousuário clicar no nome da coluna de cabeçalho [nom] associada ao campo [nom] do [DataGrid]. Aqui, definimos [nom], de modo que a cláusula de ordenação será [order by nom]. Veremos que modificaremos essa cláusula para que seja [order by nom asc] ou [order by nom desc], dependendo da ordem de classificação escolhida pelo usuário.
Além disso, criamos duas colunas de botões:

A coluna [Modifier, Mettre à jour, Annuler] nos permitirá modificar um produto, e a coluna [Supprimer], excluí-lo. Cada uma dessas colunas pode ser configurada. A coluna [Modifier, Mettre à jour, Annuler] oferece a seguinte configuração:

Vemos que é possível alterar os textos dos botões. No que diz respeito aos botões, podemos escolher entre links e botões (lista suspensa acima). Aqui, foram escolhidos os links. A configuração da coluna [Supprimer] é semelhante. Além desse assistente de configuração, utilizamos diretamente a janela de propriedades do [DataGrid] para preencher a propriedade [DataKeyField], que indica com qual campo da fonte de dados as linhas do [DataGrid] são indexadas. Nesse caso, é utilizada a chave primária da tabela de produtos:

No final, essa configuração gera o seguinte código de apresentação:
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4" AllowPaging="True" AutoGenerateColumns="False" DataKeyField="id">
<SelectedItemStyle ...></SelectedItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom"></asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix"></asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ...></PagerStyle>
</asp:DataGrid>
Como sempre, depois de adquirir alguma experiência, é possível escrever todo ou parte do código acima diretamente.
Os demais contêineres [DataGrid] possuem a configuração padrão obtida pela geração automática das colunas do [DataGrid] a partir das colunas da fonte de dados à qual ele está associado.
O contêiner [Repeater] serve para exibir uma lista de erros. Sua configuração é feita diretamente no código de apresentação:
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Cada linha do componente exibe o valor [Container.DataItem], c.a.d. O valor correspondente da lista de dados será do tipo [ArrayList] e representará uma lista de erros.
9.6.4. O código de apresentação do aplicativo
Este está localizado no arquivo [main.aspx]:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form id="Form1" runat="server">
<P>
<table>
<tr>
<td><FONT size="6">Options :</FONT></td>
<td>
<asp:linkbutton id="lnkFiltre" runat="server" CausesValidation="False">
Filtrage
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkMisajour" runat="server" CausesValidation="False">
Mise à jour
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkAjout" runat="server" CausesValidation="False">
Ajout
</asp:linkbutton>
</td>
</tr>
</table>
</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<td>
<asp:panel id="vueFormulaire" runat="server">
<P>Condition de filtrage sur la table LISTE. Exemple : prix<100 and
prix>50</P>
<P>
<asp:TextBox id="txtFiltre" runat="server" Columns="60"></asp:TextBox></P>
<P>Nombre de lignes par page :
<asp:TextBox id="txtPages" runat="server" Columns="3">5</asp:TextBox>
<asp:RequiredFieldValidator id="rfvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Indiquez le nombre de lignes par page"
EnableClientScript="False">
</asp:RequiredFieldValidator></P>
<P>
<asp:RangeValidator id="rvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Vous devez indiquer un nombre entre 3 et 10"
EnableClientScript="False" MaximumValue="10" MinimumValue="3" Type="Integer"
EnableViewState="False">
</asp:RangeValidator></P>
<P>
<asp:Label id="lblinfo1" runat="server"></asp:Label></P>
<P>
<asp:Button id="btnExécuter" runat="server" CausesValidation="False"
EnableViewState="False" Text="Exécuter">
</asp:Button></P>
</asp:panel>
<asp:panel id="vueProduits" runat="server">
<TABLE>
<TR>
<TD align="center" bgColor="#ff9966">
<P>Tri
<asp:RadioButton id="rdCroissant" runat="server" Text="croissant"
GroupName="rdTri" Checked="True">
</asp:RadioButton>
<asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant"
GroupName="rdTri">
</asp:RadioButton></P>
</TD>
<TD align="center" bgColor="#ff9966">Tous les produits
</TD>
<TD align="center" bgColor="#ff9966">Suppressions
</TD>
<TD align="center" bgColor="#ff9966">Modifications
</TD>
<TD align="center" bgColor="#ff9966">Ajouts
</TD>
<TR>
<TD vAlign="top">
<P>
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4"
AllowPaging="True" .... AutoGenerateColumns="False" DataKeyField="id">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom">
</asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix">
</asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour"
CancelText="Annuler" EditText="Modifier">
</asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete">
</asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ....>
</PagerStyle>
</asp:DataGrid>
</P>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid2" runat="server" ...>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid3" runat="server" ...>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ...></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid4" runat="server" ...>
<ItemStyle ....></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid5" runat="server....>
<AlternatingItemStyle ...></AlternatingItemStyle>
<HeaderStyle ..></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ....></PagerStyle>
</asp:DataGrid>
</TD>
</TR>
</TABLE>
<P></P>
<P>
<asp:Label id="lblInfo2" runat="server"></asp:Label></P>
</asp:panel>
<asp:panel id="vueErreurs" runat="server">
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</asp:panel>
<asp:panel id="vueAjout" EnableViewState="False" Runat="server">
<P>Ajout d'un produit</P>
<P>
<TABLE ... border="1">
<TR>
<TD>nom</TD>
<TD>
<asp:TextBox id="txtNom" runat="server" Columns="30"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvNom" runat="server" ControlToValidate="txtNom"
ErrorMessage="Vous devez indiquer un nom">
</asp:RequiredFieldValidator>
</TD>
</TR>
<TR>
<TD>prix</TD>
<TD>
<asp:TextBox id="txtPrix" runat="server" Columns="10"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="Vous devez indiquer un prix">
</asp:RequiredFieldValidator>
<asp:CompareValidator id="cvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="CompareValidator" Type="Double" Operator="GreaterThanEqual"
ValueToCompare="0">
</asp:CompareValidator>
</TD>
</TR>
</TABLE>
</P>
<P>
<asp:Button id="btnAjouter" runat="server" CausesValidation="False"
Text="Ajouter">
</asp:Button></P>
<P>
<asp:Label id="lblInfo3" runat="server"></asp:Label></P>
</asp:panel>
</td>
</tr>
</table>
</form>
</body>
</HTML>
Observe os seguintes pontos:
- a página é composta por quatro contêineres (painéis) [vueFormulaire, vueProduits, vueAjout, vueErreurs], que formarão as quatro visualizações do aplicativo.
- Os botões ou links do tipo [submit] possuem a propriedade [CausesValidation=false]. A propriedade [causesValidation=true] faz com que todas as verificações de validade da página sejam executadas. No entanto, neste caso, nem todas as verificações de validade precisam ser realizadas ao mesmo tempo. Por exemplo, ao fazer uma adição, não queremos que as verificações relativas ao número de linhas por página sejam executadas. Portanto, especificaremos nós mesmos quais verificações de validade devem ser realizadas.
9.6.5. O código de controle [global.asax]
O controlador [global.asax] é o seguinte:
O código associado [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' recuperamos as informações de configuração
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
If IsNothing(chaînedeConnexion) Then erreurs.Add("Le paramètre [OLEDBStringConnection] n'a pas été initialisé")
If IsNothing(defaultProduitsPage) Then
erreurs.Add("Le paramètre [defaultProduitsPage] n'a pas été initialisé")
Else
Try
Dim defProduitsPage As Integer = CType(defaultProduitsPage, Integer)
If defProduitsPage <= 0 Then Throw New Exception
Catch ex As Exception
erreurs.Add("Le paramètre [defaultProduitsPage] a une valeur incorrecte")
End Try
End If
' há erros de configuração?
If erreurs.Count <> 0 Then
' registram-se os erros
Application("erreurs") = erreurs
' sai
Exit Sub
End If
' aqui não há erros de configuração
' cria-se um objeto de produtos
Dim dtProduits As DataTable
Try
dtProduits = New produits(chaînedeConnexion).getProduits
Catch ex As ExceptionProduits
'ocorreu um erro de acesso aos produtos; isso é registrado no aplicativo
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' erro não tratado
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' saída da subrotina
End Try
' aqui não há erros de inicialização
' armazenamos o número de produtos por página
Application("defaultProduitsPage") = defaultProduitsPage
' armazenando a tabela de produtos
Application("dtProduits") = dtProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inicialização das variáveis de sessão
If IsNothing(Application("erreurs")) Then
' visualização da tabela de produtos
Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
' número de produtos por página
Session("nbProduitsPage") = Application("defaultProduitsPage")
' página atual exibida
Session("pageCourante") = 0
End If
End Sub
End Class
No [Application_Start], começa-se por recuperar duas informações do arquivo de configuração [web.config] do aplicativo:
- OLEDBStringConnection: a string OLEDB de conexão com o banco de dados de produtos
- defaultProduitsPage: o número padrão de produtos por página exibida
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="OLEDBStringConnection" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms3\vs\majproduits1\produits.mdb" />
<add key="defaultProduitsPage" value="5" />
</appSettings>
</configuration>
Se uma dessas duas informações estiver ausente, uma lista de erros é gerada e inserida no aplicativo. O mesmo ocorre se o parâmetro [defaultProduitsPage] existir, mas estiver incorreto. Se os dois parâmetros esperados estiverem presentes e corretos, uma tabela [dtProduits] é criada e inserida no aplicativo. É essa tabela que será utilizada e atualizada pelos diversos clientes. O banco de dados, por sua vez, permanecerá inalterado. Abordaremos a atualização deste em um próximo aplicativo. Essa tabela é criada a partir de uma instância da classe [produits], analisada anteriormente, e do método [getProduits] dessa classe. A obtenção da tabela [dtProduits] pode falhar. Nesse caso, sabe-se que a classe [produits] lança uma exceção do tipo [ExceptionProduits]. Ela é interceptada aqui e a lista de erros associada é inserida na aplicação associada à chave [erreurs]. A presença dessa chave nas informações registradas no aplicativo será verificada a cada processamento de solicitação. Se for encontrada, a visualização [erreurs] será enviada ao cliente.
Embora a tabela [dtProduits] seja compartilhada por todos os clientes da web, cada um deles terá, no entanto, sua própria visualização [dvProduits] sobre ela. De fato, cada cliente web tem a possibilidade de definir um filtro na tabela [dtProduits], bem como uma ordem de classificação. Essas informações específicas de cada cliente web são armazenadas na visão do cliente. Por isso, ela é criada na tabela [Session_Start] para ser colocada na sessão específica de cada cliente. Utiliza-se o atributo [DefaultView] da tabela [dtProduits] para obter uma visualização padrão da tabela. Inicialmente, não há filtro nem ordem de classificação. Além disso, duas informações também são inseridas na sessão:
- o número de produtos por página da chave [nbProduitsPage]. Ao iniciar a sessão, esse número é igual ao número padrão definido no arquivo de configuração.
- o número da página atual dos produtos. Inicialmente, é a primeira página.
9.6.6. O código de controle [main.aspx.vb]
A estrutura do controlador é a seguinte:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Imports System.Web.UI.WebControls
Public Class main
Inherits System.Web.UI.Page
' componentes da página
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtFiltre As System.Web.UI.WebControls.TextBox
Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
Protected WithEvents lblinfo1 As System.Web.UI.WebControls.Label
Protected WithEvents lblErreurs As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents vueProduits As System.Web.UI.WebControls.Panel
Protected WithEvents vueAjout As System.Web.UI.WebControls.Panel
Protected WithEvents lnkMisajour As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkFiltre As System.Web.UI.WebControls.LinkButton
Protected WithEvents txtNom As System.Web.UI.WebControls.TextBox
Protected WithEvents txtPrix As System.Web.UI.WebControls.TextBox
Protected WithEvents btnAjouter As System.Web.UI.WebControls.Button
Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
Protected WithEvents rfvLignes As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rvLignes As System.Web.UI.WebControls.RangeValidator
Protected WithEvents rfvNom As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rfvPrix As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents cvPrix As System.Web.UI.WebControls.CompareValidator
Protected WithEvents lnkAjout As System.Web.UI.WebControls.LinkButton
Protected WithEvents DataGrid3 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid4 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid5 As System.Web.UI.WebControls.DataGrid
' dados da página
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
Protected erreur As Boolean = False
' carregamento da página
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheErreurs()
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
....
End Sub
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
...
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
...
End Sub
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
....
End Sub
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
....
End Sub
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
...
End Sub
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
...
End Sub
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
...
End Sub
Private Sub supprimerProduit(ByVal idProduit As Integer)
...
End Sub
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
....
End Sub
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
...
End Sub
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
...
End Sub
Private Sub afficheAjout()
...
End Sub
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
....
End Sub
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
....
End Sub
End Class
9.6.7. Dados de instância
A classe [main] utiliza os seguintes dados de instância:
Public Class main
Inherits System.Web.UI.Page
' componentes da página
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...
' dados da página
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
a tabela [DataTable] de produtos — comum a todos os clientes | |
a visão [DataView] dos produtos — específica para cada cliente | |
número de produtos por página proposto por padrão |
9.6.8. O procedimento [Page_Load] para carregamento da página
O código do procedimento [page_Load] é o seguinte:
' carregamento da página
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' verificando se o aplicativo está com erro
If Not IsNothing(Application("erreurs")) Then
' o aplicativo não foi inicializado corretamente
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' recuperando uma referência da tabela de produtos
dtProduits = CType(Application("dtProduits"), DataTable)
' recuperando a visualização dos produtos
dvProduits = CType(Session("dvProduits"), DataView)
' recuperando o número de produtos por página
defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
' cancelando uma possível atualização em andamento
DataGrid1.EditItemIndex = -1
'1ª consulta
If Not IsPostBack Then
' exibe-se o formulário inicial
txtPages.Text = defaultProduitsPage.ToString
afficheFormulaire()
End If
End Sub
- Primeiramente, verifica-se se a tabela de produtos foi carregada ao iniciar o aplicativo. Caso contrário, exibe-se a visualização [erreurs] com as mensagens de erro apropriadas e, em seguida, encerra-se o procedimento após definir a variável booleana [erreur] como verdadeira. Esse indicador será testado por alguns procedimentos.
- Recupera-se a tabela de produtos [dtProduits] na aplicação e ela é armazenada na variável de instância [dtProduits] para que seja compartilhada por todos os métodos da página.
- Fazemos o mesmo com a visualização [dvProduits], recuperada da sessão, e com o número padrão de produtos por página, recuperado do aplicativo.
- Se for a primeira solicitação do cliente, exibimos o formulário de definição das condições de filtragem e paginação com uma paginação padrão de [defaultProduitsPage] produtos por página.
- Cancela-se o modo de edição do componente [dataGrid1]. Esse componente possui um atributo [EditItemIndex], que é o índice do elemento que está sendo editado. Se [EditItemIndex]=-1, então não há nenhum elemento sendo editado. Se [EditItemIndex]=i, então o elemento nº i do componente [DataGrid] está sendo editado. Ele é, então, apresentado de maneira diferente dos demais elementos do [dataGrid]:

Acima, o elemento [Produit8, 80] está no modo [édition]. Vê-se acima que o usuário pode confirmar sua atualização pelo link [Mettre à jour] e cancelá-la pelo link [Annuler]. Ele também pode utilizar outros links que não tenham nada a ver com o elemento que está sendo editado. Ao inserir [DataGrid1.EditItemIndex=-1] a cada carregamento da página, garantimos que o modo de edição de [DataGrid1] seja cancelado sistematicamente. Só se atribuirá a ele um valor diferente se um link [Modifier] tiver sido clicado. Isso será feito no procedimento que trata desse evento. Teremos as seguintes situações:
- o link [Modifier] do elemento nº 8 de [DataGrid1] foi clicado. [Page_Load], em primeiro lugar, atribui -1 a [DataGrid1.EditItemIndex]. Em seguida, o procedimento que processa o evento [Modifier] inserirá 8 em [DataGrid1.EditItemIndex]. Por fim, quando a página for reenviada ao cliente, o elemento nº 8 estará efetivamente no modo [édition] e aparecerá conforme mostrado acima.
- O usuário altera o produto que está no modo [édition] e confirma a alteração por meio do link [Mettre à jour]. O [Page_Load] insere -1 no [DataGrid1.EditItemIndex]. Em seguida, o procedimento que processa o evento [Mettre à jour] será executado e o elemento nº 8 de [dtProduits] será atualizado. Quando a página for reenviada ao cliente, nenhum elemento de [DataGrid1] estará no modo de edição (DataGrid.EditItemIndex=-1).
- Se um usuário tiver iniciado a atualização de um produto, seria lógico que ele cancelasse essa atualização por meio de [Annuler]. No entanto, nada o impede de clicar em outro link. Portanto, precisamos prever esse caso. Nesse caso, assim como nos anteriores, o [Page_Load] começa inserindo -1 no [DataGrid1.EditItemIndex]; em seguida, o procedimento que trata do evento ocorrido será executado. Ela não alterará a propriedade [DataGrid1.EditItemIndex], que permanecerá, portanto, com o valor -1. Quando a página for reenviada ao cliente, nenhum elemento de [DataGrid1] estará no modo de edição.
Vemos que, ao definir [EditItemIndex] como -1 ao carregar a página, evitamos ter que nos preocupar se o usuário estava ou não em modo de atualização quando clicou em um link.
9.6.9. Exibição das visualizações [erreurs], [formulaire] e [ajout]
A exibição da visualização [erreurs] é solicitada ao carregar a página [Page_Load], caso se constate que o aplicativo não foi inicializado corretamente. Essa exibição é feita associando o componente de dados [rptErreurs] à lista de erros passada como parâmetro e tornando visível o contêiner apropriado. Por fim, os três links [Filtre, Mise à jour, Ajout] são ocultados, pois, em caso de erro, o aplicativo não pode ser utilizado.
Private Sub afficheErreurs(ByVal erreurs As ArrayList)
' associa-se a lista de erros ao repetidor rptErreurs
With rptErreurs
.DataSource = erreurs
.DataBind()
End With
': as opções são desativadas
lnkAjout.Visible = False
lnkMisajour.Visible = False
lnkFiltre.Visible = False
' exibe-se a visualização [erreurs]
vueErreurs.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub
As demais visualizações são solicitadas a partir das opções oferecidas ao usuário:

A exibição da visualização [Ajout] é solicitada quando o usuário clica no link [Ajout] da página:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' exibe a visualização [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' exibe a visualização de adição
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
A exibição da visão [Formulaire] é solicitada quando o usuário clica no link [Filtrage] da página. Nesse momento, deve-se apresentar a visão que permite definir
- o filtro na tabela de produtos
- o número de produtos exibidos em cada página da web
É exibido um formulário que retoma os valores armazenados na sessão:
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
' definir as condições de filtragem
txtFiltre.Text = dvProduits.RowFilter
' definir a paginação
txtPages.Text = CType(Session("nbProduitsPage"), String)
' exibe o formulário de filtragem
afficheFormulaire()
End Sub
A condição de filtragem de uma visualização é definida em seu atributo [RowFilter]. Vale lembrar que a visualização filtrada e paginada se chama [dvProduits] e foi recuperada na sessão ao carregar a página [Page_Load]. A condição de filtragem é, portanto, recuperada em [dvProduits.RowFilter]. O número de produtos por página também é recuperado da sessão. Se o usuário nunca tiver definido essa informação, esse número será igual ao número padrão de produtos por página. Feito isso, é exibido o formulário que permite definir essas duas informações:
Private Sub afficheFormulaire()
' exibe a visualização [formulaire]
vueFormulaire.Visible = True
vueErreurs.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub

9.6.10. Validação da visualização [formulaire]
O clique no botão [Exécuter] acima é processado pelo seguinte procedimento:
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
' página válida?
rfvLignes.Validate()
rvLignes.Validate()
If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
afficheFormulaire()
Exit Sub
End If
' anexação dos dados filtrados à tabela
Try
dvProduits.RowFilter = txtFiltre.Text.Trim
Catch ex As Exception
lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
afficheFormulaire()
Exit Sub
End Try
' armazenamento do número de produtos por página
Session("nbProduitsPage") = txtPages.Text
'e a página atual
Session("pageCourante") = 0
' tudo certo — exibe-se os dados
afficheProduits(0, CType(txtPages.Text, Integer))
End Sub
Vale lembrar, antes de mais nada, que a página possui dois componentes de validação:
n.º | nome | tipo | função |
RequiredFieldValidator | verifica se há um valor em [txtPages] | ||
RangeValidator | verifica se txtPages está no intervalo [3,10] |
O procedimento começa executando o código de verificação dos dois componentes acima por meio de seu método [Validate] e, em seguida, verifica o valor de seu atributo [IsValid]. Esse valor será [true] somente se os dados verificados forem considerados válidos. Se os testes de validade de um dos dois componentes falharem, a visualização [formulaire] é exibida novamente para que o usuário corrija seu(s) erro(s). A condição de filtragem é aplicada ao atributo [dvProduits.RowFilter]. Aqui, pode ocorrer uma exceção se o usuário tiver definido um critério de filtragem incorreto, conforme mostrado abaixo:

Nesse caso, a visualização [formulaire] é exibida novamente. Se as duas informações inseridas estiverem corretas, dois dados são armazenados na sessão:
- o número de produtos por página escolhido pelo usuário
- o número da página que será exibida — inicialmente, a página 0
Essas duas informações são utilizadas sempre que a visualização [produits] é exibida.
9.6.11. Exibição da visualização [produits]
A visualização [produits] é a seguinte:

Temos 5 componentes [DataGrid], cada um exibindo uma visão específica da tabela de produtos [dtProduits]. Começando pela esquerda:
nome | função |
grade de exibição de uma visualização filtrada da tabela de produtos. O filtro é aquele definido pela visualização [formulaire]. Também temos .AllowPaging=true, .AllowSorting=true | |
exibe a tabela completa de produtos — permite acompanhar as atualizações | |
exibirá os produtos excluídos na tabela de produtos | |
exibirá os produtos modificados na tabela de produtos | |
exibirá os produtos adicionados na tabela de produtos |
O procedimento [afficheProduits] é responsável por exibir a visualização anterior:
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
' os dados são anexados aos dois componentes [DataGrid]
Application.Lock()
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Dim dvSupp As DataView = New DataView(dtProduits)
dvSupp.RowStateFilter = DataViewRowState.Deleted
With DataGrid3
.DataSource = dvSupp
.DataBind()
End With
Dim dvModif As DataView = New DataView(dtProduits)
dvModif.RowStateFilter = DataViewRowState.ModifiedOriginal
With DataGrid4
.DataSource = dvModif
.DataBind()
End With
Dim dvAjout As DataView = New DataView(dtProduits)
dvAjout.RowStateFilter = DataViewRowState.Added
With DataGrid5
.DataSource = dvAjout
.DataBind()
End With
Application.UnLock()
' a exibição [produits] é exibida
vueProduits.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
vueAjout.Visible = False
' a página atual é salva
Session("pageCourante") = page
End Sub
A procedimento aceita dois parâmetros:
- o número da página [page] a ser exibida em [DataGrid1]
- o número de produtos [taillePage] por página
A ligação do [DataGrid] à tabela de produtos é feita por meio de 5 visualizações diferentes.
- [DataGrid1] está vinculada à visualização paginada e ordenada [dvProduits].
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
É durante essa ligação do [DataGrid1] à sua fonte de dados que são necessárias as duas informações passadas como parâmetros para a procedimento.
- [DataGrid2] está vinculado a uma visualização que exibe todos os elementos atuais da tabela [dtProduits]. Assim como [DataGrid1], ela apresenta uma visualização atualizada da tabela [dtProduits], mas sem paginação nem ordenação.
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Utilizamos aqui o atributo [RowStateFilter] da classe [DataView]. Uma linha de uma tabela ou, neste caso, de uma visualização, possui um atributo [RowState] que indica o status da linha. Aqui estão alguns exemplos:
- (continuação)
- [Added]: a linha foi adicionada
- [Modified]: a linha foi modificada
- [Deleted]: a linha foi excluída
- [Unchanged]: a linha não sofreu alterações
Quando a tabela [dtProduits] foi criada inicialmente em [Application_Start], todas as suas linhas estavam no estado [Unchanged]. Esse estado irá evoluir à medida que os clientes da web forem atualizados:
- (continuação)
- quando um cliente cria um novo produto, uma linha é adicionada à tabela [dtProduits]. Ela ficará no estado [Added].
- quando um produto é modificado, seu estado passa de [Unchanged] para [Modified].
- Quando um produto é excluído, a linha não é excluída fisicamente. Em vez disso, ela é marcada como a ser excluída e seu estado passa para [Deleted]. É possível cancelar essa exclusão.
O atributo [RowStateFilter] da classe [DataView] permite filtrar uma visualização de acordo com o status [RowState] de suas linhas. Seus valores possíveis são os da enumeração [DataRowViewState]:
- (continuação)
- DataRowViewState.CurrentRows: as linhas não excluídas são exibidas com seus valores atuais
- DataRowViewState.Added: as linhas adicionadas são exibidas com seus valores atuais
- DataRowViewState.Deleted: as linhas excluídas são exibidas com seus valores originais
- DataRowViewState.ModifiedOriginal: as linhas modificadas são exibidas com seus valores originais
- DataRowViewState.ModifiedCurrent: as linhas modificadas são exibidas com seus valores atuais
Uma linha modificada possui dois valores: o valor original, que é aquele que a linha possuía antes da primeira modificação, e o valor atual, obtido após uma ou mais modificações. Esses dois valores são mantidos simultaneamente.
Os DataGrid 2 a 5 exibem uma visão da tabela [dtProduits] filtrada pelo atributo [RowStateFilter]
DataGrid | Filtro |
RowStateFilter= DataRowViewState.CurrentRows | |
RowStateFilter= DataRowViewState.Deleted | |
RowStateFilter= DataRowViewState.ModifiedOriginal | |
RowStateFilter= DataRowViewState.Added |
A tabela [dtPoduits] é atualizada simultaneamente por diferentes clientes da web, o que pode causar conflitos de acesso à tabela. Quando um cliente exibe suas visualizações da tabela [dtProduits], queremos evitar que ele faça isso enquanto a tabela estiver em um estado instável, pois está sendo modificada por outro cliente web. Portanto, sempre que um cliente precisar da tabela [dtProduits] para leitura, como descrito acima, ou para gravação — ao adicionar, modificar ou excluir produtos —, ele se sincronizará com os outros clientes por meio da seguinte sequência:
Podem existir várias seções críticas no código do aplicativo:
O mecanismo funciona da seguinte forma:
- um ou mais clientes chegam à instrução [Application.Lock] da seção crítica 1. Trata-se de um distribuidor de token de entrada único. Apenas um cliente recebe esse token. Vamos chamá-lo de C1.
- Enquanto o cliente C1 não devolver o token por meio de [Application.Unlock], nenhum outro cliente tem permissão para entrar em uma seção crítica controlada por [Application.Lock]. No exemplo acima, nenhum cliente pode, portanto, entrar nas seções críticas 1 e 2.
- O cliente C1 executa [Application.Unlock] e, portanto, devolve o token de entrada. Este pode então ser concedido a outro cliente. As etapas 1 a 3 são repetidas.
Com esse mecanismo, garantiremos que apenas um cliente tenha acesso à tabela [dtProduits], seja para leitura ou gravação.
O link [Mise à jour] também exibe a visualização [Produits]:

O procedimento associado é o seguinte:
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
' exibição da visualização de produtos
afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
End Sub
É necessário exibir a visualização [produits]. Isso é feito por meio do procedimento [afficheProduits], que aceita dois parâmetros: o número da página atual a ser exibida e o número de produtos por página a serem exibidos. Essas duas informações estão na sessão, eventualmente com seus valores iniciais, caso o usuário nunca as tenha definido por conta própria.
9.6.12. Paginação e ordenação do [DataGrid1]
Já abordamos as rotinas que gerenciam a paginação e a ordenação de um [DataGrid] em outro exemplo. No caso de uma mudança de página, exibimos a visão [produits] passando o número da nova página como parâmetro para o procedimento [afficheProduits].
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' mudança de página
afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
End Sub
A procedimento [DataGrid1_SortCommand] é executada quando o usuário clica no cabeçalho de uma das colunas da [DataGrid1]. É necessário, então, atribuir a expressão de ordenação ao atributo [Sort] da visualização [dvProduits] exibida por [DataGrid1]. Essa expressão é sintaticamente equivalente à expressão de ordenação colocada após a cláusula [order by] da instrução SQL SELECT. O argumento [e] do procedimento possui um atributo [SortExpression] que nos fornece a expressão de classificação associada à coluna cujo cabeçalho foi clicado. Quando o componente [DataGrid1] foi criado, essa expressão de classificação já havia sido definida. Abaixo, a expressão definida para a coluna [nom] de [DataGrid1]:

Se a expressão de classificação não tiver sido definida durante o projeto do [DataGrid], será utilizado o nome do campo de dados associado à coluna do [DataGrid]. A ordem de classificação (crescente, decrescente) é definida aqui pelo usuário por meio dos botões de opção [rdCroissant, rd Décroissant]:

O procedimento de ordenação é o seguinte:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' ordena-se a visualização de dados
With dvProduits
.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
End With
' exibição
afficheProduits(0, DataGrid1.PageSize)
End Sub
9.6.13. Exclusão de um produto
Para excluir um produto, o usuário utiliza o link [Supprimer] na linha do produto:

O procedimento [DataGrid1_DeleteCommand] é então executado:
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
' exclusão de um produto
' chave do produto a ser excluído
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' exclusão do produto
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
erreur = True
End Try
' mudança de página?
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
' exibição de produtos
afficheProduits(page, DataGrid1.PageSize)
End Sub
A atualização dos produtos será feita por meio da chave [id] do produto. De fato, a coluna [id] da tabela [dtProduits] é a chave primária e, graças a ela, podemos localizar na tabela [dtProduits] a linha do produto que está sendo atualizada no componente [DataGrid1]. O procedimento começa, portanto, recuperando a chave do produto cujo link [Supprimer] foi clicado:
' chave do produto a ser excluído
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
Sabemos que [e.Item] é o elemento de [DataGrid1] que originou o evento. Em termos gerais, esse elemento é uma linha do [DataGrid]. [e.Item.ItemIndex] é o número da linha que originou o evento. Esse índice é relativo à página atual exibida. Assim, a primeira linha da página exibida tem a propriedade [ItemIndex=0], mesmo que tenha o número 17 na tabela de produtos. [DataGrid1.DataKeys] é a lista de chaves do [DataGrid]. Como, na concepção, definimos [DataGrid1.DataKey=id], as chaves de [DataGrid1] são constituídas pelos valores da coluna [id] da tabela [dtProduits], que é, ao mesmo tempo, a chave primária. Assim, [DataGrid1.DataKeys(e.Item.ItemIndex)] é a chave [id] do produto a ser excluído. Obtida essa informação, solicitamos sua exclusão por meio do procedimento [supprimerProduit]:
' exclusão do produto
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' pb
lblInfo2.Text = ex.Message
erreur = True
End Try
Essa exclusão pode falhar em alguns casos. Veremos por quê. Nesse caso, é lançada uma exceção, que é tratada aqui. Se houver erro, o [DataGrid1] não precisa ser alterado. Apenas uma mensagem de erro é adicionada à visualização [produits]. Se não houver erro e se o usuário acabou de excluir o único produto da página atual, exibe-se a página anterior.
' mudança de página?
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
Em todos os casos, a visualização [produits] é exibida novamente com o procedimento [afficheProduits]:
O procedimento [supprimerProduit] é o seguinte:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' sincronização
Application.Lock()
' busca da linha a ser excluída
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' exclusão da linha
ligne.Delete()
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' fim da sincronização
Application.UnLock()
End Try
' lança-se uma exceção em caso de erro
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
O procedimento recebe como parâmetro a chave do produto a ser excluído. Se houvesse apenas um cliente para realizar as atualizações, essa exclusão poderia ser feita da seguinte forma:
Com vários clientes realizando as atualizações ao mesmo tempo, a situação fica mais complicada. De fato, consideremos a seguinte sequência temporal:
![]() |
Tempo | Ação |
T1 | o cliente A lê a tabela [dtProduits] — há um registro com chave 20 |
T2 | O cliente B consulta a tabela [dtProduits] — há um produto com a chave 20 |
T3 | o cliente A exclui o produto com a chave 20 |
T4 | o cliente B exclui o produto com chave 20 |
Quando os clientes A e B leem a tabela de produtos e exibem seu conteúdo em uma página da web, a tabela contém o produto com chave 20. Portanto, eles podem querer realizar uma ação nesse produto, por exemplo, excluí-lo. Inevitavelmente, um dos dois fará isso primeiro. Aquele que acessar em seguida tentará excluir um produto que já não existe mais. Esse caso é tratado pelo procedimento [supprimerProduits] de várias maneiras:
- primeiramente, há uma sincronização com o [Application.Lock]. Isso significa que, quando um cliente ultrapassa essa barreira, não há outro que possa modificar ou ler a tabela de produtos. De fato, todas essas operações são sincronizadas dessa forma. Já vimos isso no caso da leitura.
- Em seguida, verifica-se se o produto que se deseja excluir existe. Se sim, ele é excluído; caso contrário, é gerada uma mensagem de erro.
' busca da linha a ser excluída
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' exclusão da linha
ligne.Delete()
End If
- saímos da sequência crítica por meio de [Application.Unlock] para permitir que os demais clientes realizem suas atualizações.
- Se a linha não puder ser excluída, o procedimento lança uma exceção associada a uma mensagem de erro.
Vejamos um exemplo. Iniciamos um cliente [Mozilla] e selecionamos imediatamente a opção [Mise à jour] (visualização parcial):

Fazemos o mesmo com um cliente [Internet Explorer] (visualização parcial):

Com o cliente [Mozilla], excluímos o produto [produit2]. Obtemos a seguinte nova página:

A operação de exclusão foi concluída com sucesso. O produto excluído aparece corretamente no [DataGrid], na lista de produtos excluídos, e não aparece mais na lista de produtos dos componentes [DataGrid1] e [DataGrid2], que exibem os produtos atualmente presentes na tabela. Vamos fazer o mesmo com o [Interbet Explorer]. Eliminamos o elemento [produit2]:

A resposta obtida é a seguinte:

Uma mensagem de erro informa ao usuário que o produto com chave [2] não existe. A resposta retorna ao cliente uma nova visualização que reflete o estado atual da tabela. Percebe-se que, de fato, o produto com chave [2] está na lista de produtos excluídos. A visualização [produits] reflete, portanto, as atualizações de todos os clientes e não de apenas um.
9.6.14. Adicionar um produto
Para adicionar um produto, o usuário utiliza o link [Ajout] nas opções. Esse link simplesmente exibe a visualização [Ajout]:


Os dois procedimentos envolvidos nessa ação são os seguintes:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' exibe a visualização [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' exibe a visualização de adição
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
Vale lembrar que a visualização [Ajout] possui componentes de validação:
nome | tipo | função |
RequiredFieldValidator | verifica se há um valor em [txtNom] | |
RequiredFieldValidator | verifica se há um valor em [txtPrix] | |
CompareValidator | verifica se preço >= 0 |
O procedimento de gerenciamento do clique no botão [Ajouter] é o seguinte:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
' adição de um novo item à tabela de produtos
' primeiro, os dados devem ser válidos
rfvNom.Validate()
rfvPrix.Validate()
cvPrix.Validate()
If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
' novamente o formulário de preenchimento
afficheAjout()
Exit Sub
End If
' criamos uma linha
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
' adicionamos a linha à tabela
Application.Lock()
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' limpeza
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
Primeiramente, são executadas as verificações de validade dos três componentes [rfvNom, rfvPrix, cvPrix]. Se alguma das verificações falhar, a visualização [Ajout] é exibida novamente com as mensagens de erro dos componentes de validação. Veja um exemplo:

Se os dados forem válidos, prepara-se a linha a ser inserida na tabela [dtProduits].
' cria-se uma linha
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
Primeiramente, cria-se uma nova linha na tabela [dtProduits] utilizando o método [DataTable.NewRow]. Essa linha conterá as três colunas [id, nom, prix] da tabela [dtProduits]. As colunas [nom, prix] são preenchidas com os valores inseridos no formulário de adição. A coluna [id], por sua vez, não é preenchida. Essa coluna é do tipo [AutoIncrement], c.a.d. O SGBD servirá como chave para um novo produto, sendo o valor máximo existente mais 1. A linha [produit] criada aqui está separada da tabela [dtProduits]. Agora precisamos inseri-la nessa tabela. Como vamos atualizar a tabela [dtProduits], ativamos a sincronização entre clientes:
Feito isso, tentamos adicionar a linha à tabela [dtProduits]. Se for bem-sucedido, exibimos uma mensagem de sucesso; caso contrário, uma mensagem de erro.
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' limpeza
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Normalmente, não há nenhuma exceção possível. No entanto, por precaução, implementamos um gerenciamento de exceções. Após a adição, saímos da seção crítica por meio de [Application.Unlock].
9.6.15. Modificação de um produto
Para modificar um produto, o usuário utiliza o link [Modifier] na linha do produto:

Em seguida, entra-se no modo de edição:

Graças ao componente [DataGrid], esse resultado é obtido com muito pouco código:
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
' coloca-se o elemento atual no modo de edição
DataGrid1.EditItemIndex = e.Item.ItemIndex
' reexibe os produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Ao clicar no link [Modifier], o procedimento [DataGrid1_EditCommand] é executado no servidor. O componente [DataGrid1] possui um campo [EditItemIndex]. A linha cujo índice corresponde ao valor de [EditItemIndex] é colocada no modo de edição. Cada valor da linha assim atualizada pode ser modificado em um campo de entrada, conforme mostra a captura de tela acima. Recuperamos, portanto, o índice do produto no qual ocorreu o clique no link [Modifier] e o atribuímos ao atributo [EditItemIndex] do componente [DataGrid1]. Resta apenas exibir novamente a visualização [produits]. Ela aparecerá idêntica ao que era, mas com uma linha no modo de edição.
O link [Annuler] do produto que está sendo editado permite que o usuário cancele sua atualização. O procedimento associado a esse link é o seguinte:
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
' reexibe os produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Ela simplesmente exibe novamente a visualização [produits]. Pode-se ficar tentado a inserir -1 em [DataGrid1.EditItemIndex] para cancelar o modo de atualização. Na verdade, sabemos que o procedimento [Page_Load] faz isso sistematicamente. Portanto, não há necessidade de repeti-lo.
A modificação é validada pelo link [Mettre à jour] da linha que está sendo editada. Em seguida, é executado o procedimento:
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
' chave do produto a ser modificado
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' elementos modificados
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
' as alterações são válidas?
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
' se houver erro, exibe-se novamente a página de atualização
If lblInfo2.Text <> String.Empty Then
' coloca-se a linha novamente no modo de atualização
DataGrid1.EditItemIndex = e.Item.ItemIndex
' exibição de produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' fim
Exit Sub
End If
' caso não haja erro — altera-se a tabela
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
End Try
' exibição de produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
Assim como na exclusão, precisamos recuperar a chave do produto a ser modificado para localizar sua linha na tabela de produtos [dtProduits]:
' chave do produto a ser alterado
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
Em seguida, precisamos recuperar os novos valores a serem atribuídos à linha. Esses valores estão no componente [DataGrid1]. O argumento [e] do procedimento serve para nos ajudar. [e.Item] representa a linha de [DataGrid1], que é a origem do evento. Portanto, essa é a linha que está sendo atualizada, já que o link [Mettre à jour] existe apenas nessa linha. Essa linha contém colunas designadas pela coleção [Cells] da linha. Assim, [e.Item.Cells(0)] representa a coluna 0 da linha que está sendo atualizada. Sabemos que os novos valores estão nos campos de entrada. A coleção [e.Item.Cells(i).Controls] representa a coleção de controles da coluna i da linha [e.Item]. As duas instruções a seguir recuperam os valores dos campos de entrada da linha atualizada em [DataGrid1]:
' elementos alterados
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
Portanto, temos os novos valores da linha modificada na forma de cadeias de caracteres. Em seguida, verificamos se esses dados são válidos. O nome não deve estar vazio e o preço deve ser um número positivo ou zero:
' as alterações são válidas?
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
Em caso de erro, o tag [lblInfo2] exibirá uma mensagem de erro e a página será simplesmente reexibida:
' se houver erro, exibe-se novamente a página de atualização
If lblInfo2.Text <> String.Empty Then
' coloca-se a linha no modo de atualização
DataGrid1.EditItemIndex = e.Item.ItemIndex
' exibição de produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' fim
Exit Sub
End If
O que o código acima não mostra é que os valores inseridos são perdidos. De fato, [DataGrid1] está vinculado aos dados da tabela [dtProduits], que contém os valores originais da linha modificada. Veja um exemplo.

A resposta obtida é a seguinte:

Percebe-se que os valores inseridos inicialmente foram perdidos. Em uma aplicação profissional, isso provavelmente não seria aceitável. Aqui, atingimos certos limites do modo padrão de atualização do componente [DataGrid]. Seria preferível ter uma visão [Modification] análoga à visão [Ajout].
Se os dados forem válidos, eles são utilizados para atualizar a tabela [dtProduits]:
' caso não haja erro — modifica-se a tabela
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' problema
lblInfo2.Text = ex.Message
End Try
' exibição de produtos
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
Pelo mesmo motivo mencionado durante a exclusão de um produto, a modificação pode falhar. De fato, entre o momento em que o cliente consultou a tabela [dtProduits] e aquele em que ele vai modificar um de seus produtos, este pode ter sido excluído por outro cliente. O procedimento [modifierProduit] lida com esse caso lançando uma exceção. Essa exceção é tratada aqui. Após o sucesso ou a falha da atualização, o aplicativo retorna a visualização [produits] ao cliente. Resta-nos ver como o procedimento [modifierProduit] realiza a atualização:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' sincronização
Application.Lock()
' busca da linha a ser modificada
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' modifica-se a linha
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' fim da sincronização
Application.UnLock()
End Try
' lança-se uma exceção em caso de erro
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
Não entraremos em detalhes sobre esse procedimento, cujo código é semelhante ao do procedimento [supprimerProduit], que já foi explicado detalhadamente. Vejamos apenas dois exemplos. Primeiro, alteramos o preço do produto [produit1]:

Ao utilizar o link [Mettre à jour] acima, obtemos a seguinte resposta:

A alteração está presente nos componentes [DataGrid] 1 e 2, que refletem o estado atual da tabela [dtProduits]. Ela também está presente no componente [DataGrid4], que é uma visualização das linhas modificadas, as quais são exibidas com seus valores originais. Agora, vamos examinar um caso de conflito de acesso. Assim como no exemplo de exclusão, utilizaremos dois clientes web diferentes. Um cliente [Mozilla] lê a tabela [dtProduits] e entra no modo de modificação do produto [produit1]:

Um cliente [Internet Explorer] está prestes a excluir o produto [produit1]:

O cliente [Internet Explorer] exclui o [produit1]:

Observe-se que o [produit1] não consta mais na tabela de linhas modificadas, mas sim na de linhas excluídas. O cliente [Mozilla], por sua vez, valida sua atualização:

O cliente [Mozilla] recebe a seguinte resposta à sua atualização:

Ele pode constatar que o [produit1] foi excluído, uma vez que consta na lista de produtos excluídos.
9.7. Aplicativo web para atualização da tabela física de produtos
9.7.1. Soluções propostas
A aplicação anterior era mais um caso teórico destinado a demonstrar o gerenciamento de um objeto [DataTable] em cache do que um caso comum. De fato, em algum momento, é necessário atualizar a fonte real dos dados. É possível optar por duas estratégias diferentes:
- utiliza-se o cache [dtProduits] na memória para atualizar a fonte dos dados. Pode-se criar uma página localizada na árvore de navegação da aplicação anterior para ter acesso ao cache [dtProduits] dessa aplicação. Essa página ofereceria ao administrador a possibilidade de refletir na fonte física dos dados as alterações realizadas no cache [dtProduits]. Para isso, seria possível adicionar um novo método à classe de acesso [produits], que aceitasse como parâmetro o cache [dtProduits] e, com esse cache, atualizasse a fonte física dos dados.
- A fonte física de dados é atualizada ao mesmo tempo que o cache.
A estratégia nº 1 permite abrir apenas uma conexão com a fonte física de dados. A estratégia nº 2 requer uma conexão a cada atualização. Dependendo da disponibilidade das conexões, poderá-se optar por uma ou outra estratégia. Como dispomos das ferramentas para implementá-la (a classe [produits]), escolhemos a estratégia nº 2.
9.7.2. Solução 1
Por uma questão de continuidade com o aplicativo anterior, optamos pela seguinte estratégia:
- a fonte física é atualizada ao mesmo tempo que o cache
- o cache é construído apenas uma vez, na inicialização do aplicativo na classe [global.asax.vb]. Isso significa que, se a fonte física de dados for atualizada por outros clientes que não sejam os clientes da web, estes não verão essas alterações. Eles verão apenas as alterações que eles próprios fizerem na tabela em cache.
Para atualizar a fonte física dos dados, precisamos de uma instância da classe de acesso aos produtos. Cada cliente poderia ter a sua própria. Também é possível compartilhar uma única instância, que seria criada pela aplicação ao iniciar. Essa é a solução que escolhemos aqui. O código de controle [global.asax.vb] é modificado da seguinte forma:
Imports System
Imports System.Web
...
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' recuperação das informações de configuração
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
...
' aqui não há erros de configuração
' cria-se um objeto de produtos
Dim objProduits As New produits(chaînedeConnexion)
Dim dtProduits As DataTable
Try
dtProduits = objProduits.getProduits
Catch ex As ExceptionProduits
'ocorreu um erro ao acessar os produtos; isso é registrado no aplicativo
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' erro não tratado
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' saída da subrotina
End Try
' aqui não há erros de inicialização
...
' a instância de acesso aos dados é armazenada
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
End Class
Uma instância da classe de acesso aos dados foi armazenada no aplicativo, associada à chave [objProduits]. Cada cliente utilizará essa instância para acessar a fonte física dos dados. Ela será recuperada no procedimento [Page_Load] de [main.aspx.vb]:
Protected objProduits As produits
....
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' recuperando a instância de acesso aos produtos
objProduits = CType(Application("objProduits"), produits)
...
End Sub
A instância de acesso aos dados [objProduits] está disponível para todos os métodos da página. Ela será utilizada para as três operações de atualização: adição, exclusão e modificação.
O procedimento de adição é modificado da seguinte forma:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
' adiciona-se a linha
Application.Lock()
Try
' adiciona-se a linha à tabela em cache
dtProduits.Rows.Add(produit)
' adiciona-se a linha à fonte física
Dim nouveauProduit As sProduit
With nouveauProduit
.nom = CType(produit("nom"), String)
.prix = CType(produit("prix"), Double)
End With
objProduits.ajouterProduit(nouveauProduit)
' acompanhamento
lblInfo3.Text = "Ajout réussi"
' limpeza
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
' erro
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
O procedimento de modificação é alterado da seguinte forma:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' sincronização
Application.Lock()
' busca pela linha a ser modificada
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' modificação da linha no cache
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
' modificação da linha na fonte física
Dim nouveauProduit As sProduit
With nouveauProduit
.id = idProduit
.nom = CType(ligne.Item("nom"), String)
.prix = CType(ligne.Item("prix"), Double)
End With
objProduits.modifierProduit(nouveauProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' fim da sincronização
Application.UnLock()
End Try
' lança-se uma exceção em caso de erro
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
O procedimento de exclusão é alterado da seguinte forma:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' sincronização
Application.Lock()
' busca da linha a ser excluída
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' exclusão da linha no cache
ligne.Delete()
' exclusão da linha na fonte física
objProduits.supprimerProduit(idProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' fim da sincronização
Application.UnLock()
End Try
' lança-se uma exceção em caso de erro
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
9.7.3. Testes
Partimos da seguinte tabela de dados em um arquivo ACCESS:

Um cliente web é iniciado:

Adicionamos um produto:

Excluímos [produit1]:

Alteramos o preço de [produit2]:

Após essas alterações, verificamos o conteúdo da tabela [liste] no banco de dados ACCESS:

As três alterações foram corretamente refletidas na tabela física. Vamos agora examinar um caso de conflito. Excluímos, diretamente abaixo de ACCESS, a linha de [produit2]:

Voltamos ao nosso cliente web. Ele não vê a exclusão que foi feita e, por sua vez, deseja excluir o [produit2]:

Ele recebe a seguinte resposta:

A linha [produit2] foi efetivamente removida do cache, conforme indicado na lista de produtos excluídos. A exclusão de [produit2] na fonte física, por sua vez, falhou, conforme indicado na mensagem de erro.
9.7.4. Solução 2
Na solução anterior, os clientes da web atualizam simultaneamente a fonte de dados física, mas não visualizam as alterações feitas pelos outros clientes. Eles visualizam apenas as suas próprias alterações. Agora, gostaríamos que um cliente pudesse visualizar a fonte de dados física como ela está atualmente, e não como estava no momento da inicialização do aplicativo. Para isso, ofereceremos ao cliente uma nova opção:

Com a opção [Rafraîchir], o cliente força uma nova leitura da fonte de dados física. Para que isso não afete os outros clientes, é necessário que a tabela resultante dessa leitura pertença ao cliente que está realizando a atualização e não seja compartilhada com os outros clientes. Essa é a primeira diferença em relação ao aplicativo anterior. O cache [dtProduits] da fonte de dados será criado por cada cliente e não pela própria aplicação. A modificação é feita em [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' recuperação das informações de configuração
...
' aqui não há erros de configuração
' cria-se um objeto de produtos
Dim objProduits As New produits(chaînedeConnexion)
' armazenamos o número de produtos por página
Application("defaultProduitsPage") = defaultProduitsPage
' armazenamos a instância de acesso aos dados
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inicialização das variáveis de sessão
If IsNothing(Application("erreurs")) Then
' a fonte de dados é armazenada em cache
Dim dtProduits As DataTable
Try
Application.Lock()
dtProduits = CType(Application("objProduits"), produits).getProduits
Catch ex As ExceptionProduits
'ocorreu um erro ao acessar os produtos; isso é registrado na sessão
Session("erreurs") = ex.erreurs
Exit Sub
Finally
Application.UnLock()
End Try
' o cache [dtProduits] é armazenado na sessão
Session("dtProduits") = dtProduits
' visualização da tabela de produtos
Session("dvProduits") = dtProduits.DefaultView
' número de produtos por página
Session("nbProduitsPage") = Application("defaultProduitsPage")
' página atual exibida
Session("pageCourante") = 0
End If
End Sub
End Class
As informações da sessão são recuperadas a cada solicitação no procedimento [Page_Load]:
' dados da página
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected objProduits As produits
Protected nbProduitsPage As Integer
Protected pageCourante As Integer
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' verifica se o aplicativo está com erro
If Not IsNothing(Application("erreurs")) Then
' o aplicativo não foi inicializado corretamente
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' verifica se a sessão está com erro
If Not IsNothing(Session("erreurs")) Then
' a sessão não foi inicializada corretamente
afficheErreurs(CType(Session("erreurs"), ArrayList))
Exit Sub
End If
' recupera-se uma referência da tabela de produtos
dtProduits = CType(Session("dtProduits"), DataTable)
' recuperando a visualização dos produtos
dvProduits = CType(Session("dvProduits"), DataView)
' recupera-se o número de produtos por página
nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
' recuperando a página atual
pageCourante = CType(Session("pageCourante"), Integer)
' recupera-se a instância de acesso aos produtos
objProduits = CType(Application("objProduits"), produits)
' cancela uma eventual atualização em andamento
DataGrid1.EditItemIndex = -1
'1ª consulta
If Not IsPostBack Then
' exibe-se o formulário inicial
txtPages.Text = nbProduitsPage.ToString
afficheFormulaire()
End If
End Sub
As informações recuperadas durante a sessão serão salvas no final dessa mesma sessão, após cada consulta:
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
' armazenam-se algumas informações na sessão
Session("dtProduits") = dtProduits
Session("dvProduits") = dvProduits
Session("nbProduitsPage") = nbProduitsPage
Session("pageCourante") = pageCourante
End Sub
O evento [PreRender] é aquele que sinaliza que a resposta será enviada ao cliente. Aproveitamos esse momento para salvar na sessão todos os dados que devem ser mantidos. Isso é um pouco exagerado, já que, com bastante frequência, apenas alguns deles tiveram seus valores alterados. Esse salvamento sistemático tem a vantagem de nos livrar da gestão da sessão nos demais métodos da página.
A operação de atualização do cache é realizada pelo procedimento a seguir:
Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
' é necessário atualizar o cache [dtProduits] com a fonte de dados física
' início da sincronização
Application.Lock()
Try
' a tabela de produtos
dtProduits = CType(Application("objProduits"), produits).getProduits
' armazenamos o filtro atual
Dim filtre As String = dvProduits.RowFilter
' cria-se a nova visualização filtrada
dvProduits = New DataView(dtProduits)
' reaplicando o filtro
dvProduits.RowFilter = filtre
Catch ex As ExceptionProduits
'ocorreu um erro de acesso aos produtos; isso é registrado na sessão
Session("erreurs") = ex.erreurs
' exibe-se a visualização [erreurs]
afficheErreurs(ex.erreurs)
' concluído
Exit Sub
Finally
' fim da sincronização
Application.UnLock()
End Try
' tudo correu bem — exibimos os produtos a partir da primeira página
afficheProduits(0, nbProduitsPage)
End Sub
O procedimento gera novos valores para o cache [dtProduits] e para a visualização [dvProduits]. Esses valores serão inseridos na sessão pelo procedimento [Page_PreRender] descrito acima. Assim que o cache [dtProduits] for reconstruído, os produtos são exibidos a partir da primeira página.
Segue um exemplo de execução. Um cliente [mozilla] é iniciado e exibe os produtos:

Um cliente [Internet Explorer] faz o mesmo:

O cliente [Mozilla] exclui o [produit1], modifica o [produit2] e adiciona um novo produto. Ele obtém a seguinte nova página:

O cliente [Internet Explorer] deseja excluir o [produit1].

Ele recebe a seguinte resposta:

Foi informado de que [produit1] não existia mais. O usuário decide então atualizar seu cache com o link [Rafraîchir] acima. Ele recebe a seguinte resposta:

Agora ele possui a mesma fonte de dados que o cliente [Mozilla].
9.8. Conclusion
Neste capítulo, dedicamos bastante tempo aos contêineres de dados e suas ligações com fontes de dados. Concluímos mostrando como é possível atualizar uma fonte de dados usando um componente [DataGrid]. Utilizamos uma tabela de banco de dados como fonte de dados. Embora o componente [DataGrid] facilite um pouco a representação dos dados, a verdadeira dificuldade não está na parte da apresentação, mas sim no gerenciamento das atualizações da fonte de dados realizadas pelos diferentes clientes. Podem surgir conflitos de acesso, que precisam ser gerenciados. Neste caso, nós os gerenciamos no controlador utilizando o [Application.Lock]. Provavelmente seria mais sensato sincronizar os acessos à fonte de dados na classe de acesso a ela, para que o controlador não precise se preocupar com tais detalhes que não são de sua competência.
Na prática, as tabelas de um banco de dados estão interligadas por relações, e sua atualização deve levar isso em consideração. Isso tem consequências, em primeiro lugar, na classe de acesso aos dados, que se torna mais complexa do que seria necessário para uma tabela independente. Geralmente, isso também tem consequências na parte de apresentação da aplicação web, pois muitas vezes é preciso exibir não apenas uma única tabela, mas as tabelas interligadas por relações.
Além disso, este capítulo permitiu apresentar diferentes estruturas de dados, como [DataTable, DataView].









