Clique aqui para ler todos os artigos desta edição
Edição no Datagrid
por Marcos Santos e Renato Haddad
O DataGrid é um dos controles mais utilizados em ASP.NET e permite apresentar dados de forma clara, simples e de fácil manutenção. O objetivo deste artigo é mostrar como editar dados no DataGrid e capacitar o internauta a alterá-los por meio de controles existentes, paginação de dados, Bind dos controles e eventos, bem como a montar um DataGrid dinamicamente.
O primeiro passo é criar um projeto ASP.NET Web Application chamado DataGridEditable, usando como linguagem o Visual C# com um formulário denominado EditDataGrid.aspx. Insira um DataGrid chamado gridProdutos e formate-o de acordo com a sua necessidade. A princípio, altere apenas a propriedade AutoGenerateColumns para False.
Como as colunas não serão montadas automaticamente, é preciso informar quais colunas irão preencher o DataGrid. Para isso, pressione o botão direito no gridProdutos e selecione Property Builder. Na guia Columns, localize a lista Available Columns e selecione as seguintes colunas com as devidas propriedades:
Coluna |
Header Text |
Data Field |
1 Bound Column |
ProductID |
ProductID |
|
ReadOnly = True |
|
1 Bound Column |
ProductName |
ProductName |
1 Bound Column |
UnitPrice |
UnitPrice |
|
ReadOnly = True |
|
|
Data Formatting Expression = {0:c} |
|
1 Template Column |
CategoryID |
|
1 Template Column |
Discontinued |
|
1 Button Column (tipo Edit, Update e Cancel) |
|
|
Como o DataGrid exibe todos os dados listados nas colunas do Bound Column, é possível especificar que algumas colunas não sejam editadas quando a linha estiver no modo de edição. Neste exemplo, as colunas ProductID e UnitPrice não poderão ser alteradas, portanto, aplique a propriedade Read Only como True. Veja na Figura 1 as colunas selecionadas.
Figura 1 – Seleção das colunas do DataGrid
Pressione o botão OK e note que o gridProdutos já contém as novas colunas customizadas. É possível perceber também que as colunas CategoryID e Discontinued não contêm nenhum tipo de controle.
Em nosso exemplo, usaremos o banco de dados Northwind, o que significa que na estrutura da tabela de produtos um produto pertencerá a uma determinada categoria. Quando o internauta editar o produto, o ideal é fazer com que ele selecione a categoria correspondente ao produto em um DropDownList. Para isso, precisamos criar uma coluna do tipo Template Column. A mesma analogia se aplica ao campo Discontinued, pois este campo é boleano. Com isso, iremos montar um Template Column com um CheckBox.
Para começar, pressione o botão direito no gridProdutos e selecione a opção: Edit Template / Columns[3] – CategoryID. O [3] significa que essa coluna é a quarta coluna do DataGrid. Arraste até a sessão ItemTemplate dois Labels nomeados, respectivamente, como lbCategoryDesc e lbCategoryID_busca. Para lbCategoryDesc, configure a propriedade Text como nulo; para lbCategoryID_busca, configure Visible como False. Com isso, o conteúdo não será exibido. Veja os templates na Figura 2.
O objetivo desses controles é exibir a descrição da categoria quando o gridProdutos não estiver sendo editado. O lbCategoryID_busca será usado para localizar a categoria selecionada do produto a ser editado. Para preencher esse controle, selecione a propriedade DataBindings e, na janela aberta, selecione Text na parte Bindable Properties e, em Custom Binding Expression, digite DataBinder.Eval(Container,"DataItem.CategoryID"). O DataBinder.Eval relacionará o CategoryID do DataSource ao componente lbCategoryID_busca.
A seguir, arraste um DropDownList até a sessão EditItemTemplate e nomeie (ID) como dropCategoryEdit. O objetivo desse controle é disponibilizar ao internauta todas as categorias em um DropDownList, facilitando a interface e protegendo-nos de eventuais erros de digitação (já que o internauta é obrigado a selecionar uma categoria existente).
Figura 2 – Template das colunas
Para configurar a coluna Discontinued, pressione o botão direito no gridProdutos e selecione EditTemplate / Columns[4] – Discontinued. Arraste dois checkBox até as sessões ItemTemplate e EditItemTemplate e nomeie-os como cbDiscontinuedIt e cbDiscontinuedEdit (um para cada sessão). Para cbDiscontinuedIt, defina a propriedade Enabled como False para que o internauta não possa editar sem estar no modo de edição. Para finalizar os checkBoxes, configure a propriedade DataBindings em ambos, selecionando Checked em Bindable Properties e a expressão DataBinder.Eval(Container,"DataItem.Discontinued") em Custom Binding Expression. Veja na Figura 3 estas configurações.
Figura 3 – Configuração do Checked
Para sair da edição, clique em OK e, em seguida, pressione o botão direito sobre o gridProdutos e selecione End Template Editing. Note que o gridProdutos já contém os controles personalizados definidos no Template Columns, conforme a Figura 4.
Como a quantidade de dados a serem exibidos pode ser grande, o ideal é implementar uma paginação no gridProdutos; portanto, pressione o botão direito, selecione Property Builder e, na guia Paging, configure 10 registros por página, selecionando também a opção Allow paging e em Mode, selecione a opção Page numbers para que sejam mostrados números ao invés de setas. As cores da Figura 4 podem ser definidas, clicando com o botão direito do mouse e escolhendo a opção Auto Format.
Figura 4 – Template final do gridProdutos
Códigos
O funcionamento do formulário ocorre da seguinte forma: na primeira vez que o formulário for carregado, será executada uma rotina para montar o gridProdutos e armazenar todos os dados na Session. A vantagem desse modelo é que, além de não ser necessário ir ao banco de dados todas as vezes que a página for carregada, ele atualiza apenas a Session, e não o banco de dados em si.
Como usaremos o SQL Server como exemplo e precisaremos concatenar Strings, digite na lista Using as seguintes classes:
using System.Data.SqlClient;
using System.Text;
Para definir a string de conexão com o banco de dados digite na lista Protected o código referente à sua conexão. Cabe ressaltar que essa conexão deve ser configurada de acordo com a instalação do SQL Server no servidor, o que pode incluir Password, segurança integrada, etc.
protected const string conexao = "database=northwind;server=(local);user id=sa";
No momento da edição do gridProdutos, é preciso verificar qual a categoria atual do produto a ser editado, a fim de que, quando for exibida a lista de categorias no DropDownList, essa categoria já apareça selecionada. Portanto, declare a string categoryId_busca na lista protected.
// categoryId_busca utilizado posteriormente no ItemDataBound
protected string categoryId_busca = "";
Digite o código a seguir no evento Page_Load para montar o gridProdutos. Todos os códigos contidos no Page.IsPostBack serão executados apenas na primeira vez em que página for carregada.
private void Page_Load(object sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
montaGrid();
}
}
Digite a rotina montaGrid, que seleciona todos os produtos da tabela Products, monta um DataTable e armazena a tabela na Session. Ao final, ela define a origem e preenche o gridProdutos.
//----Este evento será invocado toda vez que um PostBack tiver origem no próprio DataGrid------//
private void montaGrid()
{
// Na primeira vez que a página for carregada
// não haverá a Session "dtProducts"
DataTable dtProducts = (DataTable)Session["dtProducts"];
if (dtProducts == null)
{
// Define o Sql que retorna os
// produtos e as categorias
StringBuilder sql = new StringBuilder();
sql.Append("Select ProductID, ProductName, UnitPrice, CategoryID,");
sql.Append(" Discontinued from Products");
sql.Append(" order by productId");
// Conecta ao Banco e cria um DataSet (ds)
// com o resultado da consulta
SqlConnection conn = new SqlConnection(conexao);
SqlDataAdapter da = new SqlDataAdapter(sql.ToString(),conn);
DataSet ds = new DataSet();
da.Fill(ds,"Products");
dtProducts = ds.Tables["Products"];
// Armazena DataTable "Products" em Session
// para que seja atualizado no evento de edição
Session["dtProducts"] = dtProducts;
}
gridProdutos.DataSource = dtProducts;
gridProdutos.DataBind();
}
Observe que os dados já estão sendo exibidos no gridProdutos, mas se o internauta quiser alterar o conteúdo de um determinado produto, ele precisará selecionar o botão Edit. Usando programação, precisamos interceptar esse evento e criar o código para exibir os dados atuais e disponibilizar as colunas editáveis para possíveis alterações. Selecione o gridProdutos, pressione F4 para exibir a janela de propriedades, clique no raio (Events), localize o evento EditCommand e dê um duplo clique. Digite o seguinte código:
// Evento ao clicar no botão Edit
private void gridProdutos_EditCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
// fornece ao grid o item a ser editado
gridProdutos.EditItemIndex = e.Item.ItemIndex;
this.categoryId_busca = ((System.Web.UI.WebControls.Label)e.Item.FindControl("lbCategoryID_busca")).Text;
// o componente lbCategoryID_busca é visible=false,
// para capturar o código a ser selecionado no lookup.
montaGrid();
}
A variável categoryId_busca armazena o conteúdo do CategoryID referente ao produto editado. Isso é necessário para que, quando o internauta selecionar outra categoria no DropDownList, a categoria atual já apareça selecionada.
Em seguida, é preciso criar o evento ItemDataBound para preencher o controle Label. Esse evento representa a última oportunidade para se acessar o dado de um item (linha) antes que ele seja exposto ao usuário. Portanto, nos itens de visualização, é invocado o método retornaDescricaoCategory, que pega o CategoryId armazenado no Label lbCategoryID_busca existente no ItemTemplate da coluna CategoryID e retorna a descrição para o Label lbCategoryDesc visível ao usuário.
No Item em que está sendo requisitado o modo de edição, o DropDownList é preenchido com os registros da tabela Categories e a categoria atual já está selecionada, esta já conhecida pelo armazenamento no atributo this.categoryId_busca. Veja a Listagem 1.
Para criar este evento, selecione o gridProdutos e, na janela de propriedades, dê um clique duplo no evento ItemDataBound.
Listagem 1 – Evento ItemDataBound do gridProdutos
private void gridProdutos_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
// Verifica o Item a ser Editado
if (e.Item.ItemType == ListItemType.EditItem)
{
System.Web.UI.WebControls.DropDownList lookup = (System.Web.UI.WebControls.DropDownList)e.Item.FindControl("dropCategoryEdit");
if (lookup != null)
{
lookup.DataSource = this.retornaDataTable_Category();
lookup.DataTextField = "CategoryName";
lookup.DataValueField = "CategoryID";
lookup.DataBind();
//Já seleciona a Categoria atual
lookup.SelectedValue = lookup.Items.FindByValue(this.categoryId_busca).Value;
}
}
else //modo de Visualização
{
System.Web.UI.WebControls.Label lbCategoryID = (System.Web.UI.WebControls.Label)e.Item.FindControl("lbCategoryID_busca");
System.Web.UI.WebControls.Label lbCategoryDesc = (System.Web.UI.WebControls.Label)e.Item.FindControl("lbCategoryDesc");
if ((lbCategoryID != null) && (lbCategoryDesc != null))
lbCategoryDesc.Text = this.retornaDescricaoCategory(lbCategoryID.Text);
}
}
Veja a explicação detalhada das principais linhas do código da Listagem 1:
Verifica se está no modo de edição ou visualização.
if (e.Item.ItemType == ListItemType.EditItem)
Pesquisa o controle dropCategoryEdit no gridProdutos para saber se ele existe ou não.
System.Web.UI.WebControls.DropDownList lookup = (System.Web.UI.WebControls.DropDownList)e.Item.FindControl("dropCategoryEdit");
Se não existir, é porque está no modo de visualização de dados; caso contrário, será edição.
if (lookup != null)
Define a origem dos dados, ou seja, chama a rotina retornaDataTable_Category que retorna a lista de categorias existentes na tabela Categories.
lookup.DataSource = this.retornaDataTable_Category();
Define o que será exibido (DataTextField) e o que será armazenado (DataValueField). O DataBind preenche o respectivo controle.
lookup.DataTextField = "CategoryName";
lookup.DataValueField = "CategoryID";
lookup.DataBind();
Este código já seleciona a categoria atual do produto, afinal, o internauta precisa saber qual é a categoria atual para poder selecionar a nova categoria.
lookup.SelectedValue = lookup.Items.FindByValue(this.categoryId_busca).Value;
Caso seja apenas para visualização, pesquisa os controles que irão exibir a descrição da categoria oriunda da rotina retornaDescricaoCategory.
else
{
System.Web.UI.WebControls.Label lbCategoryID = (System.Web.UI.WebControls.Label)e.Item.FindControl("lbCategoryID_busca");
System.Web.UI.WebControls.Label lbCategoryDesc = (System.Web.UI.WebControls.Label)e.Item.FindControl("lbCategoryDesc");
if ((lbCategoryID != null) && (lbCategoryDesc != null))
lbCategoryDesc.Text = this.retornaDescricaoCategory(lbCategoryID.Text);
Como a descrição das categorias está armazenada na tabela Categories do banco de dados, para evitar que o banco de dados seja lido a cada item, é mais rápido armazenar toda a lista de categorias na Session dtCategory. A rotina a seguir será invocada a partir do ItemDataBound, tanto na edição como na visualização.
private DataTable retornaDataTable_Category()
{
if (Session["dtCategory"] == null)
{
SqlConnection conn = new SqlConnection(conexao);
SqlDataAdapter da = new SqlDataAdapter("select CategoryID, CategoryName from Categories order by CategoryName",conn);
DataSet ds = new DataSet();
da.Fill(ds,"Category");
return ds.Tables["Category"];
}
else
return (DataTable)Session["dtCategory"];
}
No código de visualização do ItemDataBound, para exibir a descrição da respectiva categoria, é preciso criar o método retornaDescricaoCategory para cada item do DataGrid.
private string retornaDescricaoCategory(object categoryID)
{
string categ = categoryID.ToString();
DataTable dt = retornaDataTable_Category();
DataRow[] dr = dt.Select("CategoryID = "+categ);
return dr[0]["CategoryName"].ToString();
}
Se o internauta estiver editando um item no gridProdutos e resolver cancelar a edição, bastará clicar no botão Cancel. No entanto, é preciso criar o código para o evento CancelCommand. Este código atribui -1 para o EditItemIndex e chama a rotina montaGrid.
private void gridProdutos_CancelCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
gridProdutos.EditItemIndex = -1;
montaGrid();
}
Se o internauta resolver salvar as alterações, será preciso criar o código correspondente para salvar os dados na Session e atualizar o gridProdutos. Quando executar este código, você perceberá que a categoria precisará ser selecionada em um DropDownList para que proporcione uma interface funcional para o gridProdutos. Note ainda que nem todos os campos são passíveis de alteração, pois são somente Read-Only.
Neste exemplo, é importante ressaltar que o código do UpdateCommand atualiza a Session. Caso queira atualizar diretamente o banco de dados, basta criar um Command com a T-SQL Update.
private void gridProdutos_UpdateCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
// Altera os dados por Referência. Não precisa salvar novamente na sessão
DataTable dt = (DataTable)Session["dtProducts"];
if (dt != null)
{
// Pega o índice do DataTable que foi alterado, vindo do DataGrid
int indice = e.Item.ItemIndex + (gridProdutos.CurrentPageIndex)*10;
// TextBox criado automaticamente com o nome do produto alterado
System.Web.UI.WebControls.TextBox txtProdName = (System.Web.UI.WebControls.TextBox)(e.Item.Cells[1].Controls[0]);
System.Web.UI.WebControls.DropDownList lookup = (System.Web.UI.WebControls.DropDownList)e.Item.FindControl("dropCategoryEdit");
System.Web.UI.WebControls.CheckBox cbDiscontinued = (System.Web.UI.WebControls.CheckBox)e.Item.FindControl("cbDiscontinuedEdit");
// CategoryID, selecionado no DropDownList no modo de edição
int categoryID = Convert.ToInt32(lookup.SelectedValue);
// Não precisa salvar novamente a session. É automatico
dt.Rows[indice]["ProductName"] = txtProdName.Text;
dt.Rows[indice]["CategoryID"] = categoryID;
dt.Rows[indice]["Discontinued"] = cbDiscontinued.Checked;
}
gridProdutos.EditItemIndex = -1;
montaGrid();
}
Para finalizar a customização do gripProdutos, resta-nos implementar o evento para provocar a paginação. Portanto, crie o código a seguir no evento PageIndexChanged, que capturará o número da página selecionada e montará o gridProdutos para que exiba os produtos da página correspondente.
private void gridProdutos_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e)
{
gridProdutos.CurrentPageIndex = e.NewPageIndex;
montaGrid();
}
Salve o projeto no Solution Explorer, defina-o no Set As Start Page e execute (CTRL + F5) para ver o resultado do gridProdutos montado. Experimente editar algum produto (por exemplo, troque o nome, a categoria e os demais dados). Navegue nas páginas e note que as alterações são mantidas sempre na Session. Uma constatação: trabalhar com a fonte de dados desconectada permite uma excelente performance.
Conclusões
Trabalhar com DataGrids pode deixar a sua aplicação robusta e fornecer uma interface profissional e de fácil entendimento aos internautas. Explore os recursos do DataGrid, os eventos e as facilidades que ele proporciona. “No stress, think .NET”.
Referências:
http://www.asp.net
http://www.gotdotnet.com
http://www.linhadecodigo.com.br