Percorrendo os Itens do DataGrid

by Israel Aece 3. December 2005 23:18

Para compor este cenário, teremos que ter uma coluna do tipo TemplateColumn no DataGrid e, dentro dela, devemos colocar um controle CheckBox que irá especificar se o registro está ou não marcado. Depois do DataGrid devidamente carregado, para cada item (linha), com excessão do Header e Footer, será criado automaticamente um controle CheckBox para que o usuário possa marcar os registros que desejar.

Agora, no evento Click de um botão qualquer, iremos percorrer os itens do DataGrid e, dentro deste laço For Each, será necessário fazer algumas verificações antes de acessar diretamente o CheckBox que está contido dentro do item. A primeira verificação deve ser feita analisando se o item é do tipo Item ou AlternatingItem, quais são as seções (linhas) que podem conter os CheckBoxes. Em seguida, através do método FindControl, recuperamos a instância do controle CheckBox e verificamos se ele está ou não marcado, através da propriedade Checked. Se estiver, recuperamos o valor da terceira célula (índice 2) através da propriedade Text da mesma e, conseqüentemente, adicionamos em um controle Label.

O exemplo abaixo mostra como recuperar no evento Click de um botão, os itens marcados de um DataGrid:

private void Button1_Click(object sender, System.EventArgs e)
{
    foreach(DataGridItem item in this.DataGrid1.Items)
    {
        if(item.ItemType == ListItemType.Item ||
            item.ItemType == ListItemType.AlternatingItem)
        {
		
            CheckBox chk = (CheckBox)item.FindControl("CheckBox1");
            if(chk.Checked)
                this.Label1.Text += item.Cells[2].Text + "<br>";
        }
    }
}

Tags:

ASP.NET

DataBinder

by Israel Aece 8. November 2005 13:34

Dando uma vasculhada pelo Help do Visual Studio .NET 2005, me deparei com um novo recurso do ASP.NET 2.0, qual apresenta uma nova opção (mais simples) para criarmos expressões de DataBinding que, no ASP.NET 1.x fazemos através do método Eval da classe DataBinder, onde em um dos seus overloads, é passado como parametro a referencia à um objeto (geralmente "Container.DataItem") que é de onde que o campo de dados será recuperado e exibido ao cliente.

Além deste parametro, é também passado uma string contendo o nome do campo ou propriedade a ser recuperada. Isso é em runtime avaliado e, consequentemente, renderizado para o cliente. Um exemplo do uso desta sintaxe é mostrada abaixo:

<%# DataBinder.Eval(Container.DataItem, "expressao") %>

expressao é o nome da propriedade a ser recuperada. Existe ainda um outro parametro para o método Eval, que recebe também uma string contendo uma possível formatação para este campo, que depois de avaliado e retornado, o ASP.NET se encarrega também de aplicar a formatação.

Tudo isso agora se resumiu à chamada de um método, ou seja, temos agora uma forma compacta de fazer isso:

<%# Eval("expressao") %>

Isso é possível graças à um método protegido que temos na classe Page chamado Eval (que é herdado da classe TemplateControl). Se decompilarmos esse método, veremos que ele faz o trabalho semelhante ao que fazíamos no HTML da versão 1.x, ou seja, invoca o método DataBinder.Eval:

Protected Friend Function Eval(ByVal expression As String) As Object
      Me.CheckPageExists
      Return DataBinder.Eval(Me.Page.GetDataItem, expression)
End Function

Está aqui uma forma facilitada, mas ainda propícia à erros, já que apenas informamos uma string, para que o mesmo possa avaliar e retornar um valor, portanto, se não existir um campo correspondente, o erro somente será detectado em runtime, quando uma Exception for atirada.

Tags:

ASP.NET

Acessando valores em UserControls - ASCX

by Israel Aece 2. November 2005 23:20

É importante dizer que precisamos armazenar o valor definido para a propriedade no ViewState, porque sem isso, um PostBack fará com que o valor seja perdido, não resultando no comportamento esperado. Isso não se faz necessário quando estamos expondo ou atribuindo um valor para um WebControl, pois o mesmo já trata de armazenar o valor no ViewState internamente.

Outra observação bastante importante que devemos nos atentar, pois infelizmente ao contrário de utilizarmos WebControls, que quando fazemos Drag & Drop no Visual Studio .NET, o mesmo declara o controle no CodeBehind para termos acesso ao mesmo. Já com os User Controls isso não acontece, obrigando-nos a declarar o controle com o mesmo ID que está no WebForm (ASPX) (ver figura ao abaixo).

Em primeiro lugar, veremos como fica a criação da propriedade que armazenará o valor para posteriormente utilizarmos no WebForm:

public string NomeUsuario{
    get{
        string nomeUsuario = ViewState["NomeUsuario"] as string;
        if(nomeUsuario != null)
            return nomeUsuario;

        return string.Empty;
    }
    set{
        ViewState["NomeUsuario"] = value;
    }
}

Analisando o código acima, temos uma simples propriedade que recebe e retorna um valor do tipo String e a armazena e recupera do ViewState. Para finalizar, depois de arrastado o User Control para o WebForm, basta irmos no CodeBehind e declarar o mesmo, seguindo as observações acima, reparando que ele seja declarado com o mesmo ID do ASPX:

protected Dados Dados1;

// Para acessar:

Dados1.NomeUsuario = "Nome do Usuário";
Response.Write(Dados1.NomeUsuario);

Tags:

ASP.NET

Inserindo dados no DropDownList

by Israel Aece 25. October 2005 13:36

Uma das coisas que se faz bastante em aplicação ASP.NET é carregar um controle do tipo DropDownList com dados vindos de uma base de dados qualquer, ou mesmo de uma lista de objetos customizados da aplicação. Depois do DataBind, inserimos um novo ListItem na coleção de Items do DropDownList, especificando na propriedade Text deste ListItem "Selecione um Registro...". Em ASP.NET 1.x, é bem simples:

Me.DropDownList1.DataSource = MinhaClasse.GetAll()
Me.DropDownList1.DataBind()
Me.DropDownList1.Items.Insert(0, New ListItem("Selecione um Registro...", String.Empty))

Só que, se estivermos utilizando o ASP.NET 2.0, onde populamos esse DropDownList através de um controle DataSource e, no evento Load tentarmos utilizar o mesmo código acima não irá funcionar. A questão é que o controle DropDownList tem uma propriedade chamada AppendDataBoundItems (que foi herdada de ListControl) onde, se a mesma estiver False (que é o seu valor default), voce não conseguirá adicionar Items no controle, pois o mesmo somente permitirá a adição de items através de data-bound.

Logo, para obter sucesso neste cenário, defina a propriedade AppendDataBoundItems para True e chegará ao resultado esperado.

Tags:

ASP.NET

Url dinâmica em Web Services

by Israel Aece 22. October 2005 23:05

Quando fazemos a Web Reference no projeto, uma classe proxy herdando de SoapHttpClientProtocol é criada. Esta classe nos fornece uma propriedade chamada URL, que armazena a URL do Web Service que o cliente está invocando. No momento desta referência, o proxy gerado define a URL baseando-se na URL de desenvolvimento (qual informamos), que provavelmente trata-se de um endereço de localhost.

É através desta propriedade que alteramos em tempo de execução no cliente a URL do serviço, desde que, esta nova URL (Web Service) implemente a mesma descrição/serviço que foi anteriormente gerado pela classe proxy. Para ilustrar isso, criamos um Web Service com um WebMethod e o referenciamos na aplicação cliente. Iremos definir a URL do Web Service no arquivo de configuração da aplicação (App.Config) para que, quando a URL mudar, não precisar recompilar a aplicação:

using System.Configuration;

WS.Consultas ws = new WS.Consultas();
ws.Url = ConfigurationSettings.AppSettings["WSURL"];
MessageBox.Show(ws.MensagemBoasVindas(Me.txtNome.Text));

Como vemos no código acima, depois que instanciamos o Web Service, recuperamos a URL do arquivo de configuração através da propriedade AppSettings e definimos na propriedade URL. Em seguida apenas chamamos o WebMethod da forma como já conhecemos. O código abaixo mostra a chave e seu valor (URL) definida no arquivo de configuração:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="WSURL" value="http://localhost/WSNovo/Consultas.asmx" />
    </appSettings>
</configuration>

Mas há ainda a possibilidade de colocarmos isso no construtor da classe Proxy para não ficarmos definindo esse parâmetro a cada instância do WebService. No Web Service temos uma propriedade chamada URLBehavior que, ao definí-la como Dynamic, automaticamente é criado uma chave dentro do arquivo App.Config contendo a URL do Web Service e, o código do construtor da classe proxy que herda de SoapHttpClientProtocol é alterado para buscar a URL Web Service dentro do arquivo de configuração da aplicação. A imagem abaixo mostra a janela de propriedades do Web Service, onde devemos definir a URL Web Service como dinâmica:

Figura 1 - Janela de propriedades do Web Service.

Depois desta alteração, o construtor da classe proxy fica:

public Consultas() {
    string urlSetting = System.Configuration.ConfigurationSettings.AppSettings["WSURL"];
    if ((urlSetting != null)) {
        this.Url = string.Concat(urlSetting, "");
    }
    else {
        this.Url = "http://localhost/WS/Service1.asmx";
    }
}

Tags: ,

ASP.NET | CSD

Criando um DataGrid em Runtime

by Israel Aece 22. October 2005 17:16

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á-los. 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";

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);
}

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();
}

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);
}

É 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);
    }
}

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);
}

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();
    }
}

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.

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();
    }
}

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();
}

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();
}

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();
    }
}

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.

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();
    }
}

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;
}

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);
}

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?"");");
}

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.

DataGridRuntime.zip (48.40 kb)

Tags:

ASP.NET

Imagens dinâmicas do ASP.NET 2.0

by Israel Aece 18. October 2005 13:38

Podemos no ASP.NET 1.x criar e manipular imagens dinamicamente e assim exibir ao usuário. Utilizamos bastante isso em nosso dia à dia, quando precisamos recuperar um conteúdo (imagem) de uma base de dados, imagens do disco e até mesmo quando necessitamos criar uma imagem randômica, contendo letras e números para gerar Captchas. Infelizmente com a infraestrutura do ASP.NET 1.x, somente podemos definir no controle Image uma URL contendo o caminho até a imagem ou, se for uma imagem dinâmica, optar por uma das alternativas abaixo:

  • Criação de uma página ASPX: Esta opção consiste em criar uma página ASPX e, no evento Load desta, recuperamos os dados de uma base de dados, ou mesmo de um Stream e através da propriedade OutputStream da classe Response "escrevemos" a imagem na tela. Depois disso, definimos esta página criada na propriedade ImageUrl do controle Image. Apesar de satisfazer as necessidades, isso não é uma técnica muito interessante, pois temos toda a infraestrutura de uma página, que obrigatoriamente herdará da classe Page e, não necessitamos de tudo o que ela nos fornece para algo tão simples.

  • Implementação da Interface IHttpHandler: Através desta interface, podemos implementar o método ProcessRequest que, através do parâmetro do tipo HttpContext, temos todos os recursos disponíveis da requisição corrente. Neste método criamos ou recuperamos a imagem (se for uma base de dados) e da mesma forma que vimos anteriormente, utilizamos a propriedade OutputStream para "escrever" a imagem na tela. Feito isso, definimos esse Handler no arquivo Web.Config da aplicação para interceptar a requisição e, conseqüentemente, exibir a imagem. Este artigo exibe um exemplo de como fazer isso passo à passo.

Ambas as situações exigem do desenvolver uma grande atenção, pois primeiramente precisamos saber escolher a melhor forma de gerar isso. E como já disse anteriormente, há a limitação do controle Image somente aceitar arquivos em sua propriedade ImageUrl. Se tivermos um um Array de Bytes ou mesmo um objeto do tipo Bitmap com uma imagem, não temos alternativa de definí-los no controle Image a não ser criar uma página (ou Handler) para exibir essa imagem, pois não podemos atribuir esse Array ou Bitmap diretamente ao controle Image. Isso obriga o desenvolvedor escrever um código diferente para cada uma das ocasiões. Nesse momento conseguimos notar a deficiência deste controle.

Felizmente no ASP.NET 2.0 temos, ou melhor, tínhamos uma infraestrutra bem interessante para trabalharmos com imagens dinâmicas. Essa infraestrutura também nos fornece(ia) um controle para atender todas as necessidades, ou seja, a fonte da imagem pode ser um arquivo físico, do banco de dados ou até mesmo de um Array. Esse controle chama-se DynamicImage Control, que ao contrário do controle Image, que somente é um wrapper para a tag img do HTML, nos fornece uma série de propriedades e funcionalidades para atender todas as necessidades que o controle Image não atende. A Micrsoft decidiu retirar isso e, podemos confirmar isso neste link:

"Site counters, DynamicImage Control, and the Image Generation Service
After receiving feedback and conducting internal testing and evaluation, we have decided that these features require significant additions or changes in response to customer feedback, or significant additional testing to meet stability or scalability requirements. We have decided to remove these features for ASP.NET 2.0, and will look at adding them in a future version."


Felizmente, Dino Esposito na coluna CuttingEdge da MSDN Magazine, escreveu em Abril de 2004 um artigo chamado Image Generation Service for ASP.NET 1.1. Não conheço os internals do controle DynamicImage, mas pelo que li a respeito dele, o controle/artigo do Dino substitui o mesmo, inclusive na versão 2.0 do ASP.NET. Acredito que, a única modificação seria utilizar o handler específico que temos no ASP.NET 2.0, os user defined handlers (WebHandlers).

Tags:

ASP.NET

Controle Calendar e Banco de Dados

by Israel Aece 12. October 2005 23:23

Nestes cenários o que se utiliza bastante é o controle Calendar, qual é disponibilizado pelo ASP.NET já intrinsicamente. Para o cenário de exemplo, vamos ter uma tabela que armazena os eventos de tecnologia de uma determinada região. Apenas para ilustrar, eu estarei criando um objeto do tipo DataTable para armazenar estes eventos. Em um cenário real, esses dados devem se originar de uma base de dados (ou um outro repositório qualquer), armazenar temporariamente em um objeto do tipo DataTable e, através do evento DayRender do controle Calendar, que é disparado a cada Data (dia) que será renderizado na tela, efetuamos a consulta para aquela data no objeto DataTable temporário e, se existir, atribuimos a célula correspondente o nome do evento.

É importante ressalvar que, não se deve ir a todo momento fazer a pesquisa diretamente na base de dados, em outras palavras, efetuar essa pesquisa no evento DayRender, pois isso comprometerá a performance da página/aplicação.

Para alcançarmos o objetivo, devemos criar um membro privado do tipo DataTable, que este armazenará os dados temporariamente provenientes da base dados. Vejamos abaixo o código completo:

private DataTable _dtEventos;

private void Page_Load(object sender, System.EventArgs e)
{
    if(!Page.IsPostBack)
        this.ResgataDados();
}

private void ResgataDados(){
    this._dtEventos = new DataTable();

    this._dtEventos.Columns.Add(new DataColumn("Nome", typeof(String)));
    this._dtEventos.Columns.Add(new DataColumn("Data", typeof(DateTime)));

    DataRow evento1 = this._dtEventos.NewRow();
    evento1[0] = "Evento Microsoft";
    evento1[1] = "19/10/2005";
    this._dtEventos.Rows.Add(evento1);

    DataRow evento2 = this._dtEventos.NewRow();
    evento2[0] = "Evento Linha de Código";
    evento2[1] = "25/10/2005";
    this._dtEventos.Rows.Add(evento2);
}

private void Calendar1_DayRender(
    object sender, 
    System.Web.UI.WebControls.DayRenderEventArgs e)
{
    string query = string.Concat("Data = #", e.Day.Date.ToString("MM/dd/yyyy"), "#");
    DataRow[] dr = this._dtEventos.Select(query);
    if(dr.Length > 0)
        e.Cell.Text = Convert.ToString(dr[0][0]);
}

Como podemos ver, no evento Load do WebForm e instanciamos e criamos a estrutura do objeto DataTable que é membro desta página. Para o exemplo, duas colunas estão sendo criadas: Nome do evento e a Data em que o evento irá acontecer. Logo em seguida, dois objetos do tipo DataRow são criados. quais representarão os registros de uma base de dados e, consequentemente são adicionados na coleção de linhas do objeto DataTable.

Como o evento DayRender do controle Calendar acontece depois do evento Load do WebForm, quando o mesmo for invocado, já teremos o objeto DataTable populado com os dados da base de dados e, é exatamente neste momento que a consulta deverá ser realizada para exibir ou não o nome do evento para o usuário, caso a consulta resulte em algum registro. Como já disse acima, o evento DayRender é disparado para cada data (dia) que será mostrada pelo calendário e, neste mesmo evento, recebemos como parâmetro um objeto do tipo DayRenderEventArgs que fornece toda a infraestrura para manipularmos a célula (dia) corrente.

Através do método Select do DataTable fazemos a consulta na coluna "Data" do membro DataTable, passando como parâmetro o dia corrente e, se existir um evento para esta data que está sendo consultada, definimos o nome deste evento para ser exibido ao usuário.

Tags:

ASP.NET

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Indicações

Introdução ao ASP.NET Web API - e-Book