Desenvolvimento - Visual Basic .NET

VB.NET: Implementando um DropdDownList com Sort e ItemDatabound()

Este artigo demonstra que com um pouco de criatividade, uso de herança e interfaces, podemos implementar recursos em controles já existente, tornando-os mais poderosos.

por Fernando Cerqueira



Este artigo discute:
  • Construção de um Web Server Control
  • Interface IComparer.

Este artigo usa as seguintes tecnologias:

  • Visual Basic .NET
  • Framework 1.1

Umas das coisas que mais me agradou quando conheci pela primeira vez o DataList e o DataGrid Control, foi o evento ItemDatabound(). Este evento foi exposto ao desenvolvedor para personalizar os Childs controls, registro a registro, dando a liberdade para implementar qualquer regra ou apresentação necessária aos dados antes de sua apresentação.

Infelizmente o Dropdownlist não possui esta facilidade. Muitas das vezes o leitor se depara com a necessidade de executar um DataBind() em uma DropDownList preenchendo a propriedade "Text" ou mesmo a "Value" com uma concatenação de mais de um campos ou mesmo interagindo com uma regra de negócio na aplicação. Um Exemplo simples seria popular o controle com as Cidades e UF juntas. Uma alternativa é retornar o resultado em objeto (DataReader, DataTable, Arrylist, etc.) e concatenar manualmente através de código. Outra alternativa, muito usada, é fazer esta "união" dentro da própria Query ou Store Procedure. Em Ambos os casos existem algumas desvantagens: Implementar uma interação extra dentro do código para atender nas necessidades; Criar Query" s específicas apenas para atender os casos de união, etc. Outra característica ausente é a ordenação durante o DataBind() ou mesmo um método exposto para este fim.

Resolvemos dar um fim nestas limitações, criando um DropdownlistExtend que realiza esta tarefa , compartilhando com leitor a solução neste artigo.

O Controle

Antes de mostrarmos o código, vamos definir o que desejamos do controle, e os cuidados para atender as novas características.

Primeiramente desejamos que nosso controle seja capaz de disparar um evento para cada item adicionado na lista, pelo comando DataBind(). Para termos esta característica é preciso dar um Override em OnDataBinding, assumindo o controle deste evento. Assumir o controle implica também em ser o mais abstrato possível com a origem do dado, pois ele pode vir em diversos tipos: OdbcDataReader , SqlDataReader, Colletions, DataTable etc.

O Evento disparado para aplicação devera possuir parâmetros para facilitar a manipulação do registro corrente e do item a ser adicionado. Desta forma, teremos como parâmetros:

Sender: objeto que originou o disparo. Tipo de dado: Object

Item: O item corrente a ser adicionado. Tipo de dado: Listitem

CurrentPosDataBound: O registro corrente. Tipo de dado: Object

Concluído, desejamos também que nosso controle execute a ordenação dos dados, seja pela propriedade "Text ou "Value". Os itens da dropdownlist ficam em uma colletion que por sua vez, não possui a interface IComparer. Para termos o Sort será criado uma classe que implemente a interface IComparer e que faça a comparação no tipo de dados ListItem.

Criando o Controle

Listagem 1. Criação da Classe DropdownListExtend

Imports System.ComponentModel
Imports System.Web.UI

<ToolboxData("<{0}:DropdownListExtend runat=server></{0}:DropdownListExtend>")> _
Public Class DropdownListExtend
    Inherits System.Web.UI.WebControls.DropDownList

    Public Event ItemDataBound(ByVal sender As Object, _
	ByVal Item As 	System.Web.UI.WebControls.ListItem, _
	ByVal CurrentPosDataBound As Object)

    Public Enum TypeSort
        AscendText = 0
        AscendValue = 1
        DescendText = 2
        DescendValue = 3
    End Enum

    <DefaultValue(False), Category("Data")> _
    Public Property AllowSort() As Boolean
        Get
            If viewstate("AllowSort") Is Nothing Then
                Return False
            End If
            Return CType(viewstate("AllowSort"), Boolean)
        End Get
        Set(ByVal Value As Boolean)
            viewstate("AllowSort") = Value
        End Set
    End Property

    <DefaultValue(GetType(TypeSort), "AscendText"), Category("Data")> _
    Public Property SortType() As TypeSort
        Get
            If viewstate("SortType") Is Nothing Then
                Return TypeSort.AscendText
            End If
            Return CType(viewstate("SortType"), TypeSort)
        End Get
        Set(ByVal Value As TypeSort)
            viewstate("SortType") = Value
        End Set
    End Property

    Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs)
	    ...
	    ...
    End Sub

    Public Sub SortList()
	    ...
	    ...
    End Sub

    Private Class SortListClass
	    ...
	    ...
    End Class

End Class

O controle DropdownlistExtend é criado herdando do controle Dropdownlist, sendo adicionado 2 propriedades, 1 método e 1 Evento :

AllowSort - Define se os dados serão ordenados após o Databind (True/False)

SortType - Define por qual campo será ordenado. Nesta propriedade usamos uma enumeração (TypeSort) com as possíveis combinações.

SortList - Método Sort exposto ao desenvolvedor para executar ordenação, caso seja incluídos novos itens após o DataBind().

ItemDataBound - Evento disparado para cada item que é adicionando na DropdownlistExtend durante o processo do método OnDataBinding. Note também que foi usado o Viewstate para persistir os dados das propriedades entre os postback que podem ocorrer na página que conterá o controle.

Listagem 2.  Assumindo o controle do método OnDataBinding

    Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs)
        If Not DataSource Is Nothing Then
            If Me.DataTextField <> "" Or Me.DataValueField <> "" Then
                MyBase.OnDataBinding(e)
                Exit Sub
            End If
            If TypeOf DataSource Is IDataReader Then
                Dim Obj As IDataReader = CType(DataSource, IDataReader)
                While Obj.Read()
                    Dim Item As New System.Web.UI.WebControls.ListItem
                    RaiseEvent ItemDataBound(Me, Item, Obj)
                    Me.Items.Add(Item)
                End While
                Obj.Close()
            ElseIf TypeOf DataSource Is IList Then
                Dim Obj As IList = CType(DataSource, IList)
                Dim Ind As Integer
                For Ind = 0 To Obj.Count - 1
                    Dim Item As New System.Web.UI.WebControls.ListItem
                    RaiseEvent ItemDataBound(Me, Item, Obj(ind))
                    Me.Items.Add(Item)
                Next
            ElseIf TypeOf DataSource Is IEnumerable Then
                Dim Obj As IEnumerator = DataSource.GetEnumerator()
                Do While (Obj.MoveNext())
                    Dim Item As New System.Web.UI.WebControls.ListItem
                    RaiseEvent ItemDataBound(Me, Item, Obj.Current)
                    Me.Items.Add(Item)
                Loop
            ElseIf TypeOf DataSource Is IListSource Then
                Dim Obj As IList = CType(DataSource, IListSource).GetList
                Dim Ind As Integer
                For Ind = 0 To Obj.Count - 1
                    Dim Item As New System.Web.UI.WebControls.ListItem
                    RaiseEvent ItemDataBound(Me, Item, Obj(ind))
                    Me.Items.Add(Item)
                Next
            Else
                MyBase.OnDataBinding(e)
            End If
            If AllowSort Then
                SortList()
            End If
        End If
    End Sub

Primeiro é verificado se uma das propriedades "DataTextField" ou "DataValueField" foram informadas. Caso uma destas propriedades contenha dados é executado normalmente o DataBind e não é disparado o evento ItemDataBound. Estando vazias, é feita uma verificação do Tipo de Dado que esta sendo processado na propriedade DataSource. Para termos a abstração necessária a verificação é centralizada na existência de interfaces que possibilitam interação registro a registro.

O único cuidado que o desenvolvedor deverá tomar é fazer a conversão correta do tipo de registro corrente, durante o tratamento do evento no aplicativo. Como exemplo podemos citar um cenário onde é passado um DataTable como fonte dos dados. Neste caso o evento ItemDataBound ira enviar para o aplicativo um DataRowView.

Ao final do Databind é verificado se a propriedade AllowSort esta habilitada e feita a ordenação em caso positivo (AllowSort = True).

Listagem 3. Fazendo o Sort dos dados.

Public Sub SortList()
    Dim ArrItem(Me.Items.Count - 1) As System.Web.UI.WebControls.ListItem
    Me.Items.CopyTo(ArrItem, 0)
    Dim myComparer = New SortListClass(SortType)
    Array.Sort(ArrItem, myComparer)
    Me.Items.Clear()
    Dim Item As System.Web.UI.WebControls.ListItem
    For Each Item In ArrItem
        Me.Items.Add(Item)
    Next
End Sub
Private Class SortListClass
    Implements IComparer
    Private _TypeSortOrder As TypeSort
    Public Sub New()
        _TypeSortOrder = TypeSort.AscendText
    End Sub
    Public Sub New(ByVal Order As TypeSort)
        _TypeSortOrder = Order
    End Sub
    Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
       If _TypeSortOrder = TypeSort.AscendText Then
           Return CType(x, System.Web.UI.WebControls.ListItem).Text <  _
		   CType(y, System.Web.UI.WebControls.ListItem).Text
        ElseIf TypeSortOrder = TypeSort.DescendText Then
                Return CType(x, System.Web.UI.WebControls.ListItem).Text > _
			 CType(y, System.Web.UI.WebControls.ListItem).Text
        ElseIf TypeSortOrder = TypeSort.AscendValue Then
                Return CType(x, System.Web.UI.WebControls.ListItem).Value < _
			 CType(y, System.Web.UI.WebControls.ListItem).Value
        ElseIf TypeSortOrder = TypeSort.DescendValue Then
                Return CType(x, System.Web.UI.WebControls.ListItem).Value > _
			 CType(y, System.Web.UI.WebControls.ListItem).Value
        End If
    End Function
End Class

Para execução do sort foi tirado proveito do método CopyTo que retornar um array com os itens de nossa dropdownlist . A classe Array tem a capacidade de ordenar seus elementos e possui em seu método de ordenação a opção de personalizar a comparação por uma classe que implemente a interface IComparer.

A criação da classe SortListClass implementa a interface necessária e faz a comparação de acordo com o tipo passado em seu construtor, sendo personalizado para comparação para o tipo ListItem.

Consumindo o Controle

Para consumir nosso controle basta que seja feita a compilação e inserido dentro da ToolBox a referência a DLL gerada (Clicando-se com o botão direito do mouse sobre a Toolbox e escolhendo a opção "Add/Remove Itens" ). Abaixo um Exemplo de código usando o evento ItemDatabound para mostrar o Estado e UF juntos usando como fonte de dados um DataTable.

Listagem 4.  Usando o evento ItemDataBound

    Private Sub DropdownListExtend1_ItemDataBound(ByVal sender As Object, _
	ByVal Item As System.Web.UI.WebControls.ListItem, _
	ByVal CurrentPosDataBound As Object) Handles DropdownListExtend1.ItemDataBound

        Dim Obj As DataRowView = CType(CurrentPosDataBound, DataRowView)
        Item.Text = Obj.Item("uf_nome") & " - " & Obj.Item("uf")
        Item.Value = Obj.Item("uf")

    End Sub


Figura 1. O Controle e suas propriedades em Design mode.

Conclusões

Este artigo demonstra que com um pouco de criatividade, uso de herança e interfaces, podemos implementar recursos em controles já existente, tornando-os mais poderosos. Nunca o desenvolvedor teve uma plataforma tão rica em recursos como a plataforma .NET , e sabendo tirar proveito de seus recursos, poderá tornar o seu dia a dia cada vez produtivo e agradável.

Fernando Cerqueira

Fernando Cerqueira - Microsoft Most Valuable Professional em ASP.NET 2004 – 2005 – 2006 . Possui formação Acadêmica em Administração de empresas, especialista em Call Center , BI e CRM. Atuou em diversas empresas de grande porte, sempre utilizando a plataforma Microsoft. Desde 2002 vem trabalhando com o Visual Studio .NET prestando consultoria e treinamento focando os resultados em ASP.NET. Em 2003, criou o GURJ.NET - Grupo de usuários .NET do Rio de Janeiro sendo reconhecido pelo INETA. Hoje voluntariamente dedica parte de seu tempo no comitê de Web e Infra da INETA Brasil. Atualmente, como profissional, presta serviços exclusivamente na plataforma Microsoft.NET atuando como arquiteto de sistemas em diversos projetos de porte nos estados do RJ, SP e MG.