Desenvolvimento - ASP. NET

ASP.NET: Criando um DataGrid em Runtime

Depois de várias sugestões e dúvidas que vejo nos Newsgroups, decidi escrever este artigo, que tem a finalidade de explicar passo à passo como criar um controle DataGrid do ASP.NET 1.x em tempo de execução.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Depois de várias sugestões e dúvidas que vejo nos Newsgroups, decidi escrever este artigo, que tem a finalidade de explicar passo à passo como criar um controle DataGrid do ASP.NET 1.x em tempo de execução. Particularmente eu nunca precisei disto em minhas experiências no trabalho, mas vejo constantemente o pessoal utilizá-lo. A idéia basicamente será a construção de um DataGrid e suas colunas, onde teremos:

  • 2 BoundColumns: São as colunas que serão apresentadas ao usuário, contendo os valores vindos de uma base de dados qualquer.
  • 2 ButtonColumns: Essas representarão os comandos de seleção e de exclusão que o DataGrid terá.
  • 1 EditCommandColumn: Esta tem a finalidade de dispor os controles de edição e atualização dos registros que estão visíveis no DataGrid.
  • 1 TemplateColumn: Este tipo de coluna permite-nos cria-la de forma customizada, onde podemos colocar o controle que quisermos. Para o exemplo, adicionaremos um DropDownList nos itens do DataGrid.
Baseando-se nas colunas acima, já dá para ter uma idéia de como e onde pretendemos chegar com este DataGrid em tempo de execução. A imagem abaixo mostra-nos o resultado final:

Figura 1 - Resultado final do DataGrid.

Páginas e seus membros

Inicialmente precisamos declarar um objeto do tipo DataGrid, que será o controle que utilizaremos por toda a página. Ele representará o DataGrid como se o mesmo fosse arrastado da ToolBox do Visual Studio .NET para o WebForm. Além do mesmo, vamos declarar a nível de escopo de página, variáveis constantes do tipo String, que serão responsáveis por armazenar a String de conexão com a base de dados e as querys (inclusive com seus parâmetros) que serão executadas no decorrer da página e em seus devidos momentos. O código abaixo ilustra as declarações:

private DataGrid _dg;

private const string CONNECTION_STRING = "CONNECTION_STRING";
private const string UPDATE_QUERY = 
    "UPDATE Categories SET CategoryName = @Name, "
    "Description = @Desc, State = @State WHERE CategoryID = @ID";
private const string DELETE_QUERY = "DELETE FROM Categories WHERE CategoryID = @ID";
private const string SELECT_QUERY = "SELECT * FROM Categories";
Private _dg As DataGrid

Private Const CONNECTION_STRING As String = "CONNECTION_STRING"
Private Const UPDATE_QUERY As String = _
    "UPDATE Categories SET CategoryName = @Name, Description = @Desc, " & _
    "State = @State WHERE CategoryID = @ID"
Private Const DELETE_QUERY As String = "DELETE FROM Categories WHERE CategoryID = @ID"
Private Const SELECT_QUERY As String = "SELECT * FROM Categories"
C# VB.NET

O evento Page_Init

Neste evento, que ocorre antes do evento Load do WebForm, vamos criar a instância do controle DataGrid, que já o temos declarado (_dg). Em seguinda, invocaremos a função ConfigDataGrid que será responsável por configurar as propriedades do controle DataGrid e, por fim, adicionamos o controle DataGrid na coleção de controles do WebForm. Lembrando que, temos que primeiramente procurar pelo container de controles do WebForm que armazenará o controle DataGrid, ou seja, todos os controles devem estar entre as tags <form>...</form>. Abaixo visualizamos o evento já na íntegra codificado:

override protected void OnInit(EventArgs e)
{
    this._dg = new DataGrid();
    this.ConfigDataGrid();    
    this.FindControl("Form1").Controls.Add(this._dg);

    InitializeComponent();
    base.OnInit(e);
}
Private Sub Page_Init(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Init

    Me._dg = New DataGrid
    Call Me.ConfigDataGrid()
    Me.FindControl("Form1").Controls.Add(Me._dg)

    InitializeComponent()
End Sub
C# VB.NET

Configurando o DataGrid

Esta seção de configuração do DataGrid deve sempre ser executada, ou seja, sendo um PostBack ou não e, justamente por isso que devemos executar dentro do evento Page_Init, pois ele será sempre executado.

Veremos dentro deste procedimento a configuração das propriedades essenciais do DataGrid, tais como: AutoGenerateColumns, DataKeyField, AllowSorting e SelectedItemStyle.BackColor. A primeira, AutoGenerateColumns, recebe um valor booleano (True ou False) indicando se as colunas serão criadas automaticamente de acordo com a fonte de dados. Neste caso, ela será definida como False, pois vamos criá-las manualmente. Já a propriedade DataKeyField, recebe uma String contendo o nome da coluna que é chave (ID) do registro. Isso será utilizado para quando precisarmos executar as queries de Update e Delete e, veremos abaixo como recuperá-lo. Ao definir a propriedade AllowSorting para True, o DataGrid habilita o Header em formato de HyperLink para a ordenação dos registros. Já a última propriedade que descrevemos anteriormente, SelectedItemStyle.BackColor, será onde iremos definir qual a cor da linha que o item ficará quando o usuário selecioná-lo. Podemos percebê-la na Figura 1, onde a linha selecionada está na cor azul.

Ainda dentro deste mesmo procedimento, ConfigDataGrid, chamamos mais dois métodos: GenerateColumns e AssignEvents, qual veremos mais detalhadamente um pouco adiante. O código abaixo mostra a versão final do procedimento ConfigDataGrid:

private void ConfigDataGrid(){
    this._dg.AutoGenerateColumns = false;
    this._dg.DataKeyField = "CategoryID";
    this._dg.AllowSorting = true;
    this._dg.SelectedItemStyle.BackColor = Color.LightBlue;

    this.GenerateColumns();
    this.AssignEvents();
}
Private Sub ConfigDataGrid()
    Me._dg.AutoGenerateColumns = False
    Me._dg.DataKeyField = "CategoryID"
    Me._dg.AllowSorting = True
    Me._dg.SelectedItemStyle.BackColor = Color.LightBlue

    Call Me.GenerateColumns()
    Call Me.AssignEvents()
End Sub
C# VB.NET

Depois destas principais propriedades configuradas, vamos analisar umas das partes mais interessantes do DataGrid, que trata-se da criação e configuração das colunas do mesmo. Como já vimos um pouco acima, iremos criar os seguintes tipos de colunas: BoundColumn, ButtonColumn, EditCommandColumn e TemplateColumn, quais também já vimos cada uma de suas utilidades acima. Estas não tem nada especial em sua criação. Todas consistem em instanciá-las, configurar algumas propriedades e, finalmente, adicionar na coleção de colunas do DataGrid. A única que merece uma atenção especial, é a TemplateColumn, que necessita a criação de uma classe que implemente a interface ITemplate. Esta interface define um método chamado InstantiateIn, qual é utilizado para criar e popular controles que são declarados em uma seção Template no arquivo ASPX. Abaixo veremos apenas a criação e configuração das colunas do DataGrid:

private void GenerateColumns(){
    ButtonColumn sel = new ButtonColumn();
    sel.ButtonType = ButtonColumnType.LinkButton;
    sel.CommandName = "Select";
    sel.Text = "Selecionar";
    this._dg.Columns.Add(sel);

    ButtonColumn delete = new ButtonColumn();
    delete.CommandName = "Delete";
    delete.ButtonType = ButtonColumnType.LinkButton;
    delete.Text = "X";
    this._dg.Columns.Add(delete);

    EditCommandColumn edit = new EditCommandColumn();
    edit.EditText = "Alterar";
    edit.CancelText = "Cancelar";
    edit.UpdateText = "Atualizar";
    this._dg.Columns.Add(edit);

    BoundColumn bc1 = new BoundColumn();
    bc1.DataField = "CategoryName";
    bc1.HeaderText = "Nome";
    bc1.SortExpression = "CategoryName";
    this._dg.Columns.Add(bc1);

    BoundColumn bc2 = new BoundColumn();
    bc2.DataField = "Description";
    bc2.HeaderText = "Descrição";
    bc2.SortExpression = "Description";
    this._dg.Columns.Add(bc2);

    TemplateColumn template = new TemplateColumn();
    template.ItemTemplate = new DDLTemplate();
    this._dg.Columns.Add(template);
}
Private Sub GenerateColumns()
    Dim sel As New ButtonColumn
    sel.ButtonType = ButtonColumnType.LinkButton
    sel.CommandName = "Select"
    sel.Text = "Selecionar"
    Me._dg.Columns.Add(sel)

    Dim delete As New ButtonColumn
    delete.CommandName = "Delete"
    delete.ButtonType = ButtonColumnType.LinkButton
    delete.Text = "X"
    Me._dg.Columns.Add(delete)

    Dim edit As New EditCommandColumn
    edit.EditText = "Alterar"
    edit.CancelText = "Cancelar"
    edit.UpdateText = "Atualizar"
    Me._dg.Columns.Add(edit)

    Dim bc1 As New BoundColumn
    bc1.DataField = "CategoryName"
    bc1.HeaderText = "Nome"
    bc1.SortExpression = "CategoryName"
    Me._dg.Columns.Add(bc1)

    Dim bc2 As New BoundColumn
    bc2.DataField = "Description"
    bc2.HeaderText = "Descrição"
    bc2.SortExpression = "Description"
    Me._dg.Columns.Add(bc2)

    Dim template As New TemplateColumn
    template.ItemTemplate = New DDLTemplate
    Me._dg.Columns.Add(template)
End Sub
C# VB.NET
function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i É importante dizer que, a coluna do tipo BoundColumn fornece duas propriedades importantes chamadas DataField e SortExpression onde a primeira corresponde ao campo da fonte de dados que será exibido e é retornado pela query de seleção de registros. Já o segundo, definimos uma String que será utilizada para enviar para a query de seleção fazer a ordenação através do evento SortCommand. Por fim, a coluna do tipo TemplateColumn, recebe uma instância da classe DDLTemplate, qual também criamos, populamos e adicionamos um controle do tipo DropDownList:

class DDLTemplate : ITemplate
{
    public void InstantiateIn(Control container)
    {
        DropDownList ddl = new DropDownList();
    	ddl.ID = "State";
	ddl.Items.Add(new ListItem("RJ", "RJ"));
	ddl.Items.Add(new ListItem("SC", "SC"));
	ddl.Items.Add(new ListItem("SP", "SP"));
	container.Controls.Add(ddl);
    }
}
Private Class DDLTemplate
    Implements ITemplate

    Public Sub InstantiateIn(ByVal container As Control) Implements ITemplate.InstantiateIn
        Dim ddl As New DropDownList
        ddl.ID = "State"
        ddl.Items.Add(New ListItem("RJ", "RJ"))
        ddl.Items.Add(New ListItem("SC", "SC"))
        ddl.Items.Add(New ListItem("SP", "SP"))
        container.Controls.Add(ddl)
    End Sub
End Class
C# VB.NET

Nota: A classe TemplateColumn nos fornece várias propriedades interessantes, entre elas: EditItemTemplate, FooterTemplate, HeaderTemplate e ItemTemplate. Com estas propriedades podemos definir um Template para cada seção, ou seja, no caso do exemplo deste artigo, estamos atribuindo um controle DropDownList no ItemTemplate desta TemplateColumn. Se por acaso, desejarmos atribuir um controle, ou até mesmo esse DropDownList no Header do DataGrid, basta criarmos uma nova instância de uma classe que implemente a interface ITemplate e adicionarmos na propriedade HeaderTemplate. O mesmo vale para EditItemTemplate ou FooterTemplate.

A configuração do DataGrid está finalizada, pois já definimos todas as propriedades e as colunas necessárias. Agora, o que falta é a criação dos eventos que o DataGrid irá ter e executar. Sendo eles:

  • EditCommand: Disparado sempre quando desejarmos colocar um registro em modo de edição.
  • CancelCommand: Cancela a edição do registro, não efetuando as possíveis alterações na base de dados.
  • UpdateCommand: Atualiza o registro no base de dados.
  • DeleteCommand: Exclui o registro da base de dados.
  • ItemDataBound: Disparado sempre quando um novo registro é adicionado no DataGrid. Este evento ocorre quando efetuamos o DataBind no controle DataGrid.
  • SelectedIndexChanged: É executado quando selecionamos um determinado registro no Datagrid. É disparado quando clicamos no botão "Selecionar", qual criamos através de um ButtonColumn.
  • SortCommand: Disparado quando o usuário clicar em um dos HyperLinks do Header do DataGrid para efetuar a ordenação.

Como não estamos arrastando o DataGrid da ToolBox do Visual Studio .NET para o WebForm, os eventos devem ser criados manualmente, ou seja, para cada um desses eventos, obrigatoriamente devemos criar um procedimento para ser executado quando a ação ocorrer. Vale lembrar que, cada um destes procedimentos devem ter a mesma assinatura do delegate já definido pela estrutura do ASP.NET. Veremos:

  • EditCommand: Object, DataGridCommandEventArgs.
  • CancelCommand: Object, DataGridCommandEventArgs.
  • UpdateCommand: Object, DataGridCommandEventArgs.
  • DeleteCommand: Object, DataGridCommandEventArgs.
  • ItemDataBound: Object, DataGridItemEventArgs.
  • SelectedIndexChanged: Object, EventArgs.
  • SortCommand: Object, DataGridSortCommandEventArgs.

Antes de entrarmos detalhadamente em cada um destes procedimentos, veremos como "assinar" cada um deles. Em Visual Basic .NET, utilizamos a keyword AddHandler em conjunto com a AddressOf. Já no Visual C#, criamos uma instância do delegate que atende aquele comando. O código abaixo mostra como realizar esse processo:

private void AssignEvents(){
    this._dg.EditCommand += new DataGridCommandEventHandler(this.DataGrid_Edit);
    this._dg.CancelCommand += new DataGridCommandEventHandler(this.DataGrid_Cancel);
    this._dg.UpdateCommand += new DataGridCommandEventHandler(this.DataGrid_Update);
    this._dg.DeleteCommand += new DataGridCommandEventHandler(this.DataGrid_Delete);
    this._dg.ItemDataBound += new DataGridItemEventHandler(this.DataGrid_ItemDataBound);
    this._dg.SelectedIndexChanged += new EventHandler(this.DataGrid_SelectIndexChanged);
    this._dg.SortCommand += new DataGridSortCommandEventHandler(this.DataGrid_Sort);
}
Private Sub AssignEvents()
    AddHandler Me._dg.EditCommand, AddressOf Me.DataGrid_Edit
    AddHandler Me._dg.CancelCommand, AddressOf Me.DataGrid_Cancel
    AddHandler Me._dg.UpdateCommand, AddressOf Me.DataGrid_Update
    AddHandler Me._dg.DeleteCommand, AddressOf Me.DataGrid_Delete
    AddHandler Me._dg.ItemDataBound, AddressOf Me.DataGrid_ItemDataBound
    AddHandler Me._dg.SelectedIndexChanged, AddressOf Me.DataGrid_SelectIndexChanged
    AddHandler Me._dg.SortCommand, AddressOf Me.DataGrid_Sort
End Sub
C# VB.NET

Evento Load e Carga do DataGrid

Até o presente momento fizemos tudo o que o Visual Studio .NET faria por nós se estivessemos utilizando o designer para a criação do controle. A partir de agora, começaremos a escrever códigos independentemente se estivermos ou não criando o DataGrid em tempo de execução. Primeiramente veremos o evento Load do WebForm, que deveremos carregar o DataGrid somente se não for PostBack:

private void Page_Load(object sender, System.EventArgs e)
{
    if(!Page.IsPostBack)
    {
        this.BindDataGrid();
    }
}
Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load

    If Not Page.IsPostBack Then
        Call Me.BindDataGrid()
    End If
End Sub
C# VB.NET

Nota: Muitos acreditam e colocam a criação do DataGrid neste evento e dentro desta condicional que verifica se é ou não PostBack. Com isso, o controle somente será criado na primeira execução da página, ou seja, se clicar no botão de "Alterar", o DataGrid irá sumir e, não é este o comportamento que esperamos do mesmo. Volto a dizer: a criação deve acontecer sempre, independentemente de ser ou não PostBack. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Notem que invocamos uma função chamada BindDataGrid, qual é responsável por resgatar os dados da base de dados e preencher o DataGrid. Criamos uma função para isso justamente porque precisamos disso por todo o código, ou seja, quando acontecer uma atualização ou exclusão de dados, temos que novamente invocar este procedimento para sempre preencher o DataGrid com os dados mais atuais. Como o artigo é baseado em SQL Server, estarei utilizando o Provider System.Data.SqlClient:

private void BindDataGrid(){
    this.BindDataGrid(string.Empty);
}

private void BindDataGrid(string sort){
    SqlConnection conn = new SqlConnection(CONNECTION_STRING);
    string query = SELECT_QUERY;

    if(sort != string.Empty)
    	query += " ORDER BY " + sort + " ASC";

    SqlCommand cmd = new SqlCommand(query, conn);
    SqlDataReader dr = null;
    try{
        conn.Open();
	dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
	this._dg.DataSource = dr;
	this._dg.DataBind();
    }finally{
	if(dr != null) dr.Close();
    }
}
Private Sub BindDataGrid()
    Call Me.BindDataGrid(String.Empty)
End Sub

Private Sub BindDataGrid(ByVal sort As String)
    Dim conn As New SqlConnection(Me.CONNECTION_STRING)
    Dim query As String = Me.SELECT_QUERY

    If Not sort = String.Empty Then
        query &= " ORDER BY " & sort & " ASC"
    End If

    Dim cmd As New SqlCommand(query, conn)
    Dim dr As SqlDataReader
    Try
        conn.Open()
        dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)
        Me._dg.DataSource = dr
        Me._dg.DataBind()
    Finally
        If Not IsNothing(dr) Then dr.Close()
    End Try
End Sub
C# VB.NET

Como podemos reparar no código acima, criamos dois métodos sobrecarregados, onde em um desses métodos, recebe uma String que corresponde à coluna que serão ordenados os registros, qual será sempre chamado no evento SortCommand do DataGrid. Definimos também a conexão com a base de dados, e através de um objeto do tipo SqlCommand, passamos a query a ser executada, que está definida na constante SELECT_QUERY e, retornamos um objeto do tipo SqlDataReader que, conseqüentemente, definimos na propriedade DataSource do DataGrid.

Codificando os Eventos

Depois dos eventos já assinados, ou seja, atribuímos a cada um deles o procedimento a ser executado quando a ação acontecer, devemos neste momento codificar cada um deles. Vamos detalhar cada um dos eventos do DataGrid começando pelo evento EditCommand. Este evento, será disparado quando o usuário clicar no botão "Alterar" do DataGrid. Neste momento, devemos definir a propriedade EditItemIndex com o índice da linha que será editada. Conseguímos recuperar o índice através do "e" que vem como parâmetro, qual nos fornece uma propriedade chamada ItemIndex e, em seguida, chamamos novamente o método BindDataGrid para carregar o DataGrid, mas agora, o mesmo já será apresentado com o registro que o usuário requisitou em modo de edição. O código e a figura abaixo ilustram esse processo:

private void DataGrid_Edit(object sender, DataGridCommandEventArgs e)
{
    this._dg.EditItemIndex = e.Item.ItemIndex;
    this.BindDataGrid();
}
Private Sub DataGrid_Edit(ByVal sender As Object, _
    ByVal e As DataGridCommandEventArgs)

    Me._dg.EditItemIndex = e.Item.ItemIndex
    Call Me.BindDataGrid()
End Sub
C# VB.NET

Figura 2 - DataGrid em modo de edição.

Na seqüência veremos o evento CancelCommand, que tem a finalidade de cancelar o modo de edição, descartando as possíves alterações que o usuário tenha feito no registro. É basicamente o mesmo que fizemos no evento acima, com a exceção de definir a propriedade EditItemIndex do DataGrid para -1, que indica que nenhum registro deverá ficar em modo de edição:

private void DataGrid_Cancel(object sender, DataGridCommandEventArgs e)
{
    this._dg.EditItemIndex = -1;
    this.BindDataGrid();
}
Private Sub DataGrid_Cancel(ByVal sender As Object, _
    ByVal e As DataGridCommandEventArgs)

    Me._dg.EditItemIndex = -1
    Call Me.BindDataGrid()
End Sub
C# VB.NET

Para finalizar os eventos correspondentes à edição de registro, vamos analisar o evento UpdateCommand, qual será disparado quando o usuário clicar no botão "Atualizar" do DataGrid. Neste momento devemos recuperar o novo conteúdo que o usuário digitou e submetermos para a base de dados através de objetos que estão contidos dentro do Namespace SqlClient.

Antes de vermos como isso é feito, precisamos entender como devemos acessar estes controles (TextBox e o DropDownList que está na TemplateColumn). As formas de acessá-los são diferentes, ou seja, quando formos resgatar o conteúdo dos TextBox, que correspondem as colunas (BoundColumn) que estamos editando, temos que navegar entre a coleção de controles através dos índices até chegarmos ao controle desejado, pois através do método FindControl, "não conseguimos", já que estamos assumindo que não transformamos as colunas BoundColumn em TemplateColumn, para assim sabermos o ID que cada controle ganhou.

Já para acessar o controle que está em uma TemplateColumn, podemos tanto navegar pelo índice dos controles, como utilizar o método FindControl, que é bem mais fácil de utilizar, pois sabemos (e o definimos dentro do método InstantiateIn da classe DDLTemplate) o ID do controle DropDownList que temos nesta coluna.

Aproveito essa oportunidade para explicar como recuperar o índice (ID) da linha que está sendo atualizada: lembram da propriedade DataKeyField? Ela armazena para cada linha/registro do DataGrid o ID (o valor da coluna que definimos) e, podendo agora ser recuperado para embutí-lo na query de Update, que está armazenada na constante UPDATE_QUERY. O DataGrid fornece uma propriedade chamada DataKeys que, expõe uma coleção de chaves, onde dado um índice, ele retorna o valor correspondente desta linha. Criamos os parâmetros, com o objeto do tipo SqlParameter, passando os valores que recuperamos dos controles de dentro do DataGrid e, anexamos na coleção de parâmetros do objeto SqlCommand. Finalmente abrimos a conexão com a base de dados e, através do método ExecuteNonQuery do objeto SqlCommand, executamos a query na base dados, atualizando o registro. O código abaixo exemplifica:

private void DataGrid_Update(object sender, DataGridCommandEventArgs e){
    string name = ((TextBox)e.Item.Controls[3].Controls[0]).Text;
    string description = ((TextBox)e.Item.Controls[4].Controls[0]).Text;
    string state = ((DropDownList)e.Item.FindControl("State")).SelectedValue;
    int id = Convert.ToInt32(this._dg.DataKeys[e.Item.ItemIndex]);

    SqlConnection conn = new SqlConnection(CONNECTION_STRING);
    SqlCommand cmd = new SqlCommand(UPDATE_QUERY, conn);

    cmd.Parameters.Add(new SqlParameter("@Name", SqlDbType.VarChar)).Value = name;
    cmd.Parameters.Add(new SqlParameter("@Desc", SqlDbType.VarChar)).Value = description;
    cmd.Parameters.Add(new SqlParameter("@State", SqlDbType.VarChar)).Value = state;
    cmd.Parameters.Add(new SqlParameter("@ID", SqlDbType.Int)).Value = id;

    try
    {
        conn.Open();
        cmd.ExecuteNonQuery();
    }
    finally
    {
        if(conn != null) conn.Close();
        this._dg.EditItemIndex = -1;
        this.BindDataGrid();
    }
}
Private Sub DataGrid_Update(ByVal sender As Object, _
    ByVal e As DataGridCommandEventArgs)

    Dim name As String = DirectCast(e.Item.Controls(3).Controls(0), TextBox).Text
    Dim description As String = DirectCast(e.Item.Controls(4).Controls(0), TextBox).Text
    Dim state As String = DirectCast(e.Item.FindControl("State"), DropDownList).SelectedValue
    Dim id As Integer = Convert.ToInt32(Me._dg.DataKeys(e.Item.ItemIndex))

    Dim conn As New SqlConnection(Me.CONNECTION_STRING)
    Dim cmd As New SqlCommand(Me.UPDATE_QUERY, conn)

    cmd.Parameters.Add(New SqlParameter("@Name", SqlDbType.VarChar)).Value = name
    cmd.Parameters.Add(New SqlParameter("@Desc", SqlDbType.VarChar)).Value = description
    cmd.Parameters.Add(New SqlParameter("@State", SqlDbType.VarChar)).Value = state
    cmd.Parameters.Add(New SqlParameter("@ID", SqlDbType.Int)).Value = id

    Try
        conn.Open()
        cmd.ExecuteNonQuery()
    Finally
        If Not IsNothing(conn) Then conn.Close()
        Me._dg.EditItemIndex = -1
        Call Me.BindDataGrid()
    End Try
End Sub
C# VB.NET

Dentro do bloco Finally fechamos a conexão com a base de dados, voltamos a definir a propriedade EditItemIndex para -1 e, por fim, invocamos o procedimento BindDataGrid para recarregar o DataGrid com os novos dados. Eu omiti o bloco Catch apenas para economizar espaço, mas isso jamais deverá acontecer em produção. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i O próximo evento a analisarmos é o DeleteCommand, qual será disparado quando o usuário clicar no "X" do DataGrid, que corresponderá a exclusão do registro. O código é bem parecido com o qual vimos um pouco acima, no evento UpdateCommand, ou seja, devemos recuperar o ID do registro através da coleção de DataKeys do DataGrid para podermos criar o parâmetro para o objeto SqlCommand, definindo como Value deste parâmetro, o valor que iremos recuperar do DataKeyField. Depois disso, abrimos a conexão com a base de dados, e executamos a query de exclusão que está armazenada na constante DELETE_QUERY. Vejamos:

private void DataGrid_Delete(object sender, DataGridCommandEventArgs e){
    int id = Convert.ToInt32(this._dg.DataKeys[e.Item.ItemIndex]);

    SqlConnection conn = new SqlConnection(CONNECTION_STRING);
    SqlCommand cmd = new SqlCommand(DELETE_QUERY, conn);
    cmd.Parameters.Add(new SqlParameter("@ID", SqlDbType.Int)).Value = id;

    try
    {
        conn.Open();
        cmd.ExecuteNonQuery();
    }
    finally
    {
        if(conn != null) conn.Close();
        this.BindDataGrid();
    }
}
Private Sub DataGrid_Delete(ByVal sender As Object, _
    ByVal e As DataGridCommandEventArgs)

    Dim id As Integer = Convert.ToInt32(Me._dg.DataKeys(e.Item.ItemIndex))

    Dim conn As New SqlConnection(Me.CONNECTION_STRING)
    Dim cmd As New SqlCommand(DELETE_QUERY, conn)
    cmd.Parameters.Add(New SqlParameter("@ID", SqlDbType.Int)).Value = id

    Try
        conn.Open()
        cmd.ExecuteNonQuery()
    Finally
        If Not IsNothing(conn) Then conn.Close()
        Call Me.BindDataGrid()
    End Try
End Sub
C# VB.NET

Da mesma forma que o evento UpdateCommand, no bloco Finally fechamos a conexão com a base de dados e chamamos o procedimento BindDataGrid para recarregar novamente o DataGrid já com os novos dados.

Ainda nos eventos em que o usuário dispara, falta para completar a lista o evento SelectedIndexChanged. Este evento é disparado sempre quando o usuário clica no botão "Selecionar" do DataGrid. É sempre interessante colorirmos a linha em questão para que o usuário consiga identificar qual dos registros é que está selecionado. É justamente por isso que definimos a propriedade SelectedItemStyle.BackColor do DataGrid com uma cor (definida através da estrutura System.Drawing.Color) diferente da cor padrão dos Itens. Veremos abaixo este evento já codificado:

private void DataGrid_SelectIndexChanged(object sender, EventArgs e)
{
    DataGridItem item = this._dg.SelectedItem;
    this.Label1.Text = "Item selecionado: " + item.Cells[3].Text;
}
Private Sub DataGrid_SelectIndexChanged(ByVal sender As Object, _
    ByVal e As EventArgs)

    Dim item As DataGridItem = Me._dg.SelectedItem
    Me.Label1.Text = "Item selecionado: " & item.Cells(3).Text
End Sub
C# VB.NET

Através da propriedade SelectedItem do DataGrid, recuperamos uma instância de um objeto do tipo DataGridItem (toda linha do DataGrid é um objeto do tipo DataGridItem) e assim, temos acesso a todas as informações da linha selecionada pelo usuário. Com isso, atribuímos à propriedade Text de um controle Label, o valor contido na BoundColumn, que no caso do exemplo deste artigo, é o nome da Categoria. Muitas pessoas querem que, quando o usuário selecione um item do DataGrid, os demais dados sejam apresentados em um outro Datagrid ou mesmo em outros controles do WebForm, gerando assim uma espécie de DetailsView do registro. O evento SelectIndexChanged é o ideal para este cenário, podendo adotar a mesma técnica que expliquei aqui.

Ainda temos o evento SortCommand, qual será disparado quando o usuário clicar em algum dois HyperLinks do Header do DataGrid. Dentro deste evento, chamaremos o procedimento BindDataGrid passando como parâmetro o valor da propriedade SortExpression que vem como parâmetro para o evento. Com isso, uma nova query será criada e, o DataGrid será carregado com os registros já ordenados de acordo com o que o usuário requisitou. O código abaixo mostra-nos a chamada à este método sobrecarregado dentro do evento SortCommand:

private void DataGrid_Sort(object sender, DataGridSortCommandEventArgs e)
{
    this.BindDataGrid(e.SortExpression);
}
Private Sub DataGrid_Sort(ByVal sender As Object, _
    ByVal e As DataGridSortCommandEventArgs)

    Me.BindDataGrid(e.SortExpression)
End Sub
C# VB.NET

Para finalizarmos os eventos, falta explicar o evento ItemDataBound. Este por sua vez, é sempre disparado a cada novo registro que está sendo incluído no DataGrid através do DataBind, ou seja, ocorre automaticamente quando chamamos o método DataBind do DataGrid. Dentro dele, conseguimos interceptar os valores e controles que serão exibidos pelo DataGrid antes de serem renderizados para o cliente. Neste momento, é que vamos adicionar no coleção de atributos do controle LinkButton de exclusão um código Javascript que será responsável pela confirmação de exclusão do registro. É também aqui que iremos atribuir o valor da coluna "Estado" proveniente da base de dados à propriedade SelectedValue do controle DropDownList. Antes de nos aprofundarmos no evento, vamos visualizar o evento já codificado:

private void DataGrid_ItemDataBound(object sender, DataGridItemEventArgs e){
    if(e.Item.ItemType == ListItemType.AlternatingItem || 
        e.Item.ItemType == ListItemType.Item || 
	e.Item.ItemType == ListItemType.SelectedItem)
    {
        this.SetJavaScript(e.Item);

        DropDownList ddl = (DropDownList)e.Item.FindControl("State");
        ddl.SelectedValue = ((DbDataRecord)e.Item.DataItem)["State"].ToString();
        ddl.Enabled = false;
    }
    else if(e.Item.ItemType == ListItemType.EditItem)
    {
        this.SetJavaScript(e.Item);

        ((DropDownList)e.Item.FindControl("State")).SelectedValue = 
            ((DbDataRecord)e.Item.DataItem)["State"].ToString();
    }
}

private void SetJavaScript(DataGridItem item){
    LinkButton lnk = (LinkButton)item.Controls[1].Controls[0];
    lnk.Attributes.Add("onClick", 
        @"return confirm (""Tem certeza que deseja excluir o produto?"");");
}
Private Sub DataGrid_ItemDataBound(ByVal sender As Object, _
    ByVal e As DataGridItemEventArgs)

    If e.Item.ItemType = ListItemType.AlternatingItem OrElse _
        e.Item.ItemType = ListItemType.Item OrElse _
        e.Item.ItemType = ListItemType.SelectedItem Then

        Me.SetJavaScript(e.Item)

        Dim ddl As DropDownList = DirectCast(e.Item.FindControl("State"), DropDownList)
        ddl.SelectedValue = DirectCast(e.Item.DataItem, DbDataRecord).Item("State").ToString()
        ddl.Enabled = False
    ElseIf e.Item.ItemType = ListItemType.EditItem Then
        Me.SetJavaScript(e.Item)

        DirectCast(e.Item.FindControl("State"), DropDownList).SelectedValue = _
            DirectCast(e.Item.DataItem, DbDataRecord).Item("State").ToString()
    End If
End Sub

Private Sub SetJavaScript(ByVal item As DataGridItem)
    Dim lnk As LinkButton = item.Controls(1).Controls(0)
    lnk.Attributes.Add("onClick", _
        "return confirm (""Tem certeza que deseja excluir o produto?"");")
End Sub
C# VB.NET

Antes de qualquer coisa, algumas condições devem ser verificadas para que não ocorra nenhum erro. A começar que devemos verificar o tipo da linha que está sendo inserida no controle através do Enumerador ListItemType. Os dados em suas versões normais, estão sempre nas linhas do tipo Item, AlternatingItem ou SelectedItem e, para certificarmos que não estamos inserindo ou tentando acessar um controle que não exista, devemos fazer esta verificação, mesmo porque muitas vezes não utilizamos as linhas do DataGrid que são do tipo Header e Footer para inserir os dados da base de dados, mas sim para uma sumarizar uma determinada coluna, ou algo do tipo, que no caso de um simples DataBind devem ser desconsideradas.

No exemplo do artigo, podemos acessar o controle DropDownList em quatro momentos, pois não temos ele somente quando a linha entrar em modo de edição, mas também quando os registros estão sendo apresentados em sua forma normal e quando a linha estiver selecionada. A verificação do tipo da linha aqui é para que quando o usuário estiver com o registro em modo normal, o controle DropDownList seja apresentado, mas ele não poderá ser acessado, ou seja, define-se a propriedade Enabled do mesmo para False e, ao contrário, quando estiver em modo de edição, o usuário poderá acessá-lo para poder definir um "Estado" diferente, se assim desejar.

Também criamos um procedimento auxiliar chamado SetJavaScriptConfig, qual será responsável por atribuir na coleção de atributos do LinkButton responsável pelo Exclusão ("X"), o código JavaScript de confirmação da exclusão do registro. A imagem abaixo exibe o JavaScript em execução:

Figura 3 - Confirmação de exclusão em funcionamento.

Paginação de dados

Para realizar a paginação dos dados, o procedimento é o mesmo: cria-se o procedimento e o define para ser executado quando o delegate PageIndexChanged for disparado. A única observação é que você não pode atribuir diretamente um objeto do tipo DataReader à propriedade DataSource do DataGrid quando for efetuar a paginação, porque através do DataReader não se consegue saber quantos registros serão retornardos, informação qual é necessária para que o DataGrid crie a infra-estrutura e disponibilize a paginação ao usuário. Como isso trata-se de algo um pouco mais complexo e mais detalhes do que vimos nesta pequena descrição, deixaremos esse tema para talvez um próximo artigo.

CONCLUSÃO: Como pudemos analisar no decorrer deste artigo, não temos muitos segredos em criar um controle DataGrid em tempo de execução. O que precisamos mesmo é nos atentar nos pequenos detalhes, como por exemplo a criação do DataGrid sendo ou não PostBack que, se não fizermos desta forma, o resultado apresentado não será o esperado. Claro que grande parte do que fizemos aqui via código, ao arrastar o controle DataGrid da ToolBox do Visual Studio .NET, parte deste trabalho/código seria realizado internamente pela IDE. De qualquer forma, fica aqui registrado como devemos proceder para a criação do controle DataGrid em tempo de execução para que se algum dia for preciso recorrer à esta forma, está aqui exemplificado.

Faça o download do código.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.