Compartilhar via


Criação de uma interface do usuário de classificação personalizada (C#)

por Scott Mitchell

Baixar PDF

Ao exibir uma longa lista de dados classificados, pode ser muito útil agrupar dados relacionados introduzindo linhas separadores. Neste tutorial, veremos como criar essa interface do usuário de classificação.

Introdução

Ao exibir uma longa lista de dados classificados em que há apenas um punhado de valores diferentes na coluna classificada, um usuário final pode achar difícil discernir onde, exatamente, os limites de diferença ocorrem. Por exemplo, há 81 produtos no banco de dados, mas apenas nove opções de categoria diferentes (oito categorias exclusivas mais a opção NULL ). Considere o caso de um usuário interessado em examinar os produtos que se enquadram na categoria Frutos do Mar. Em uma página que lista todos os produtos em um único GridView, o usuário pode decidir que sua melhor aposta é classificar os resultados por categoria, o que agrupará todos os produtos de Frutos do Mar juntos. Depois de classificar por categoria, o usuário precisa procurar a lista, procurando onde os produtos agrupados em frutos do mar começam e terminam. Como os resultados são ordenados em ordem alfabética pelo nome da categoria, encontrar os produtos de frutos do mar não é difícil, mas ainda requer uma verificação cuidadosa da lista de itens na tabela.

Para ajudar a destacar os limites entre grupos classificados, muitos sites empregam uma interface do usuário que adiciona um separador entre esses grupos. Separadores como os mostrados na Figura 1 permitem que um usuário encontre mais rapidamente um determinado grupo e identifique seus limites, bem como verifique quais grupos distintos existem nos dados.

Cada grupo de categorias está claramente identificado

Figura 1: Cada grupo de categorias está claramente identificado (clique para exibir a imagem em tamanho real)

Neste tutorial, veremos como criar essa interface do usuário de classificação.

Etapa 1: Criando um GridView Padrão e Classificável

Antes de explorarmos como aumentar o GridView para fornecer a interface de classificação aprimorada, vamos primeiro criar um GridView padrão e classificável que lista os produtos. Comece abrindo a CustomSortingUI.aspx página na PagingAndSorting pasta. Adicione um GridView à página, defina sua ID propriedade como ProductList, e associe-a a um novo ObjectDataSource. Configure o ObjectDataSource para usar a classe ProductsBLL e o método GetProducts() para selecionar registros.

Em seguida, configure o GridView de modo que ele contenha apenas o ProductName, CategoryName, SupplierName e UnitPrice BoundFields e o CheckBoxField descontinuado. Por fim, configure o GridView para dar suporte à classificação, marcando a opção "Habilitar Classificação" na marca inteligente do GridView (ou definindo sua AllowSorting propriedade como true). Depois de fazer essas adições à CustomSortingUI.aspx página, a marcação declarativa deve ser semelhante à seguinte:

<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL"></asp:ObjectDataSource>

Reserve um momento para ver nosso progresso até agora em um navegador. A Figura 2 mostra o GridView classificável quando seus dados são classificados por categoria em ordem alfabética.

Os dados do GridView classificável são ordenados por categoria

Figura 2: Os dados do GridView classificável são ordenados por categoria (clique para exibir a imagem em tamanho real)

Etapa 2: Explorando técnicas para adicionar as linhas do separador

Com o GridView genérico e classificável concluído, tudo o que falta é adicionar as linhas separadoras no GridView antes de cada grupo único e classificado. Mas como essas linhas podem ser injetadas no GridView? Essencialmente, precisamos iterar pelas linhas do GridView, determinar onde as diferenças ocorrem entre os valores na coluna classificada e, em seguida, adicionar a linha separadora apropriada. Ao pensar sobre esse problema, parece natural que a solução esteja em algum lugar no manipulador de eventos do RowDataBound GridView. Como discutimos no tutorial Formatação Personalizada Baseada em Dados , esse manipulador de eventos geralmente é usado ao aplicar a formatação em nível de linha com base nos dados da linha. No entanto, o RowDataBound manipulador de eventos não é a solução aqui, pois as linhas não podem ser adicionadas ao GridView programaticamente desse manipulador de eventos. A coleção Rows do GridView, na verdade, é somente leitura.

Para adicionar linhas adicionais ao GridView, temos três opções:

  • Adicione essas linhas do separador de metadados aos dados reais associados ao GridView
  • Depois que o GridView tiver sido associado aos dados, adicione instâncias adicionais de TableRow à coleção de controles do GridView.
  • Crie um controle de servidor personalizado que estenda o controle GridView e substitua os métodos responsáveis pela construção da estrutura do GridView

Criar um controle de servidor personalizado seria a melhor abordagem se essa funcionalidade fosse necessária em muitas páginas da Web ou em vários sites. No entanto, isso implicaria um pouco de código e uma exploração completa nas profundezas do funcionamento interno do GridView. Portanto, não consideraremos essa opção para este tutorial.

As outras duas opções, ao adicionar linhas separadoras aos dados reais sendo vinculados ao GridView e ao manipular a coleção de controles do GridView após ser vinculado - abordam o problema de maneira diferente e merecem uma discussão.

Adicionando linhas aos dados vinculados ao GridView

Quando o GridView está associado a uma fonte de dados, ele cria um GridViewRow para cada registro retornado pela fonte de dados. Portanto, podemos injetar as linhas do separador necessárias adicionando registros separadores à fonte de dados antes de associá-la ao GridView. A Figura 3 ilustra esse conceito.

Uma técnica envolve a adição de linhas de separador à fonte de dados

Figura 3: Uma técnica envolve a adição de linhas separadores à fonte de dados

Uso o termo "registros separadores" entre aspas porque não há nenhum registro separador especial; em vez disso, devemos indicar de alguma forma que um registro específico na fonte de dados serve como um separador, em vez de uma linha de dados normal. Para nossos exemplos, estamos associando uma ProductsDataTable instância ao GridView, que é composto de ProductRows. Podemos sinalizar um registro como uma linha separadora definindo sua propriedade CategoryID para -1 (uma vez que esse valor não poderia existir normalmente).

Para utilizar essa técnica, precisamos executar as seguintes etapas:

  1. Recuperar programaticamente os dados a serem associados ao GridView (uma ProductsDataTable instância)
  2. Classificar os dados com base nas propriedades SortExpression e SortDirection do GridView.
  3. Iterar através do ProductsRows no ProductsDataTable, procurando onde estão as diferenças na coluna ordenada
  4. Em cada limite de grupo, insira uma instância de registro ProductsRow separador na DataTable, em que seu CategoryID esteja definido para -1 (ou qualquer designação que tenha sido decidida para marcar um registro como um registro separador)
  5. Depois de injetar as linhas do separador, associe programaticamente os dados ao GridView

Além dessas cinco etapas, também precisamos fornecer um manipulador de eventos para o evento GridView RowDataBound . Aqui, nós checaríamos cada DataRow e determinaríamos se era uma linha separadora, uma cuja configuração de CategoryID era -1. Nesse caso, provavelmente gostaríamos de ajustar sua formatação ou o texto exibido nas células.

Usar essa técnica para injetar os limites do grupo de classificação requer um pouco mais de trabalho do que o descrito acima, pois você também precisa fornecer um manipulador de eventos para o evento Sorting do GridView e acompanhar os valores de SortExpression e SortDirection.

Manipulando a coleção de controles do GridView depois de ter sido vinculada aos dados

Em vez de enviar mensagens aos dados antes de associá-los ao GridView, podemos adicionar as linhas do separador depois que os dados tiverem sido associados ao GridView. O processo de associação de dados cria a hierarquia de controle do GridView, que na realidade é simplesmente uma Table instância composta por uma coleção de linhas, cada uma delas composta por uma coleção de células. Especificamente, a coleção de controles do GridView contém um Table objeto em sua raiz, um GridViewRow (que é derivado da classe TableRow) para cada registro no DataSource vinculado ao GridView e um TableCell objeto dentro de cada instância GridViewRow para cada campo de dados no DataSource.

Para adicionar linhas separadores entre cada grupo de classificação, podemos manipular diretamente essa hierarquia de controle depois que ela for criada. Podemos ter certeza de que a hierarquia de controle do GridView foi criada pela última vez quando a página está sendo renderizada. Portanto, essa abordagem substitui o Page método da Render classe, momento em que a hierarquia de controle final do GridView é atualizada para incluir as linhas do separador necessárias. A Figura 4 ilustra esse processo.

Uma técnica alternativa manipula a hierarquia de controle do GridView

Figura 4: Uma técnica alternativa manipula a hierarquia de controle do GridView (clique para exibir a imagem em tamanho real)

Para este tutorial, usaremos essa última abordagem para personalizar a experiência do usuário de classificação.

Observação

O código que estou apresentando neste tutorial baseia-se no exemplo fornecido na entrada do blog de Teemu Keiski, Experimentando um Pouco com Ordenação e Agrupamento no GridView.

Etapa 3: Adicionar as linhas do separador à hierarquia de controle do GridView

Como só queremos adicionar as linhas separadoras à hierarquia de controle do GridView depois que sua hierarquia de controle tiver sido criada e criada pela última vez nessa visita de página, queremos executar essa adição no final do ciclo de vida da página, mas antes que a hierarquia de controle GridView real tenha sido renderizada em HTML. O último ponto possível no qual podemos fazer isso é o Page classe s Render evento, que podemos substituir em nossa classe code-behind usando a seguinte assinatura de método:

protected override void Render(HtmlTextWriter writer)
{
    // Add code to manipulate the GridView control hierarchy
    base.Render(writer);
}

Quando o Page método original Render da classe é invocado base.Render(writer) , cada um dos controles na página será renderizado, gerando a marcação com base em sua hierarquia de controle. Portanto, é imperativo que ambos chamemos base.Render(writer), para que a página seja renderizada e manipulemos a hierarquia de controle do GridView antes da chamada base.Render(writer), para que as linhas do separador tenham sido adicionadas à hierarquia de controle do GridView antes de ela ser renderizada.

Para injetar os cabeçalhos do grupo de classificação, primeiro precisamos garantir que o usuário solicitou que os dados fossem classificados. Por padrão, o conteúdo do GridView não é classificado e, portanto, não precisamos inserir cabeçalhos de classificação de grupo.

Observação

Se você quiser que o GridView seja classificado por uma coluna específica quando a página for carregada pela primeira vez, chame o método GridView Sort na primeira visita de página (mas não em postbacks subsequentes). Para fazer isso, adicione essa chamada no Page_Load manipulador de eventos em uma if (!Page.IsPostBack) condição. Volte para as informações do tutorial sobre Paginação e Classificação dos Dados do Relatório para mais detalhes sobre o método Sort.

Supondo que os dados foram ordenados, nossa próxima tarefa é determinar por qual coluna os dados foram ordenados e examinar as linhas em busca de diferenças nos valores dessa coluna. O código a seguir garante que os dados foram classificados e localiza a coluna pela qual os dados foram classificados:

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // Determine the index and HeaderText of the column that
        //the data is sorted by
        int sortColumnIndex = -1;
        string sortColumnHeaderText = string.Empty;
        for (int i = 0; i < ProductList.Columns.Count; i++)
        {
            if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
                == 0)
            {
                sortColumnIndex = i;
                sortColumnHeaderText = ProductList.Columns[i].HeaderText;
                break;
            }
        }
        // TODO: Scan the rows for differences in the sorted column�s values
}

Se o GridView ainda não tiver sido classificado, a propriedade GridView SortExpression não terá sido definida. Portanto, só queremos adicionar as linhas do separador se essa propriedade tiver algum valor. Se isso acontecer, em seguida, precisaremos determinar o índice da coluna pela qual os dados foram classificados. Isso é feito fazendo um loop por meio da coleção Columns do GridView, pesquisando a coluna cuja propriedade SortExpression é igual à propriedade SortExpression do GridView. Além do índice da coluna, também capturamos a propriedade HeaderText, que é usada ao exibir as linhas separadoras.

Com o índice da coluna pela qual os dados são classificados, a etapa final é enumerar as linhas do GridView. Para cada linha, precisamos determinar se o valor da coluna ordenada difere do valor da coluna ordenada da linha anterior. Nesse caso, precisamos injetar uma nova GridViewRow instância na hierarquia de controle. Isso é feito com o seguinte código:

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // ... Code for finding the sorted column index removed for brevity ...
        // Reference the Table the GridView has been rendered into
        Table gridTable = (Table)ProductList.Controls[0];
        // Enumerate each TableRow, adding a sorting UI header if
        // the sorted value has changed
        string lastValue = string.Empty;
        foreach (GridViewRow gvr in ProductList.Rows)
        {
            string currentValue = gvr.Cells[sortColumnIndex].Text;
            if (lastValue.CompareTo(currentValue) != 0)
            {
                // there's been a change in value in the sorted column
                int rowIndex = gridTable.Rows.GetRowIndex(gvr);
                // Add a new sort header row
                GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
                    DataControlRowType.DataRow, DataControlRowState.Normal);
                TableCell sortCell = new TableCell();
                sortCell.ColumnSpan = ProductList.Columns.Count;
                sortCell.Text = string.Format("{0}: {1}",
                    sortColumnHeaderText, currentValue);
                sortCell.CssClass = "SortHeaderRowStyle";
                // Add sortCell to sortRow, and sortRow to gridTable
                sortRow.Cells.Add(sortCell);
                gridTable.Controls.AddAt(rowIndex, sortRow);
                // Update lastValue
                lastValue = currentValue;
            }
        }
    }
    base.Render(writer);
}

Esse código começa referenciando programaticamente o Table objeto encontrado na raiz da hierarquia de controle do GridView e criando uma variável de cadeia de caracteres chamada lastValue. lastValue é usado para comparar o valor da coluna ordenada da linha corrente com o valor da linha anterior. Em seguida, a coleção GridView Rows é enumerada e, para cada linha, o valor da coluna classificada é armazenado na currentValue variável.

Observação

Para determinar o valor da coluna ordenada de uma linha específica, uso a propriedade da célula Text. Isso funciona bem para BoundFields, mas não funcionará conforme desejado para TemplateFields, CheckBoxFields e assim por diante. Examinaremos como considerar os campos alternativos do GridView em breve.

As currentValue variáveis e as variáveis lastValue são então comparadas. Se forem diferentes, precisamos adicionar uma nova linha separador à hierarquia de controle. Isso é feito determinando o índice de GridViewRow na coleção de objetos Table, criando novas instâncias de Rows e GridViewRow, e então adicionando TableCell e TableCell à hierarquia de controle.

Observe que a linha separadora TableCell é formatada de modo que abrange toda a largura do GridView, está formatada com a classe CSS SortHeaderRowStyle, e possui a propriedade Text que mostra tanto o nome do grupo de classificação (como Categoria) quanto o valor do grupo (como Bebidas). Por fim, lastValue é atualizado para o valor de currentValue.

A classe CSS usada para formatar a linha SortHeaderRowStyle de cabeçalho do grupo de classificação precisa ser especificada no Styles.css arquivo. Fique à vontade para usar qualquer configuração de estilo que agrade a você; Usei o seguinte:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

Com o código atual, a interface de classificação adiciona cabeçalhos de grupo de classificação ao classificar por qualquer BoundField (consulte a Figura 5, que mostra uma captura de tela ao classificar por fornecedor). No entanto, ao classificar por qualquer outro tipo de campo (como CheckBoxField ou TemplateField), os cabeçalhos do grupo de classificação não são encontrados (consulte a Figura 6).

A interface de classificação inclui cabeçalhos de grupo ao classificar por BoundFields

Figura 5: A interface de classificação inclui cabeçalhos de grupo de classificação ao classificar por BoundFields (clique para exibir a imagem em tamanho real)

Os cabeçalhos do grupo de classificação estão ausentes ao classificar um CheckBoxField

Figura 6: Os cabeçalhos do grupo de classificação estão ausentes ao classificar um CheckBoxField (clique para exibir a imagem em tamanho real)

O motivo pelo qual os cabeçalhos dos grupos de ordenação estão ausentes ao realizar uma ordenação por CheckBoxField é que o código atualmente utiliza apenas a propriedade TableCellText para determinar o valor da coluna ordenada para cada linha. Para CheckBoxFields, a propriedade TableCell s Text é uma string vazia; em vez disso, o valor está disponível por meio de um controle CheckBox Web que reside na coleção TableCell s Controls.

Para lidar com tipos de campo diferentes de BoundFields, precisamos aumentar o código em que a currentValue variável é atribuída para verificar a existência de um CheckBox na coleção TableCellControls. Em vez de usar currentValue = gvr.Cells[sortColumnIndex].Text, substitua este código pelo seguinte:

string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
    if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
    {
        if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
            currentValue = "Yes";
        else
            currentValue = "No";
    }
    // ... Add other checks here if using columns with other
    //      Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
    currentValue = gvr.Cells[sortColumnIndex].Text;

Esse código examina a coluna TableCell classificada da linha atual para determinar se há controles na coleção Controls. Se houver, e o primeiro controle for um CheckBox, a variável currentValue será definida como Sim ou Não, dependendo da propriedade do CheckBox Checked. Caso contrário, o valor será obtido da propriedade de TableCell s Text. Essa lógica pode ser replicada para manipular a classificação de qualquer TemplateFields que possa existir no GridView.

Com a adição de código acima, os cabeçalhos do grupo de classificação agora estão presentes ao classificar pelo CheckBoxField descontinuado (consulte a Figura 7).

Os cabeçalhos do grupo de classificação agora estão presentes ao classificar um CheckBoxField

Figura 7: Os cabeçalhos do grupo de classificação agora estão presentes ao classificar um CheckBoxField (clique para exibir a imagem em tamanho real)

Observação

Se você tiver produtos com NULL valores de banco de dados para o CategoryID, SupplierIDou UnitPrice campos, esses valores aparecerão como cadeias de caracteres vazias no GridView por padrão, o que significa que o texto da linha do separador para esses produtos com NULL valores será lido como Categoria: (ou seja, não há nome após Categoria: como com Categoria: Bebidas). Se você quiser um valor exibido aqui, poderá definir a propriedade BoundFields NullDisplayText para o texto que deseja exibir ou adicionar uma instrução condicional no método Render ao atribuir a propriedade currentValue da linha separadora Text.

Resumo

O GridView não inclui muitas opções internas para personalizar a interface de classificação. No entanto, com um pouco de código de baixo nível, é possível ajustar a hierarquia de controle do GridView para criar uma interface mais personalizada. Neste tutorial, vimos como adicionar uma linha separador de grupo de classificação para um GridView classificável, que identifica com mais facilidade os grupos distintos e os limites desses grupos. Para obter exemplos adicionais de interfaces de classificação personalizadas, confira a entrada do blog de Scott GuthrieA Few ASP.NET 2.0 GridView Sorting Tips and Tricks.

Divirta-se programando!

Sobre o autor

Scott Mitchell, autor de sete livros asp/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser alcançado em mitchell@4GuysFromRolla.com.