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 separadoras. 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 alguns valores diferentes na coluna classificada, um usuário final pode achar difícil discernir onde, exatamente, ocorrem os limites de diferença. 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 opção é classificar os resultados por categoria, o que agrupará todos os produtos frutos do mar juntos. Depois de classificar por categoria, o usuário precisa procurar por 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 que encontra os produtos frutos do mar não é difícil, mas ainda requer a verificação atenta da lista de itens na grade.

Para ajudar a realçar 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 grupo específico e identifique seus limites, bem como verifique quais grupos distintos existem nos dados.

Cada grupo de categorias é 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 ProductListe associe-a a um novo ObjectDataSource. Configure o ObjectDataSource para usar o ProductsBLL método da classe s GetProducts() para selecionar registros.

Em seguida, configure o GridView de modo que ele contenha apenas o ProductName, CategoryName, SupplierNamee UnitPrice BoundFields e o CheckBoxField descontinuado. Por fim, configure o GridView para dar suporte à classificação marcando a caixa de seleção Habilitar Classificação na marca inteligente gridView (ou definindo sua AllowSorting propriedade truecomo ). 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 de GridView classificáveis são ordenados por categoria

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

Etapa 2: Explorando técnicas para adicionar as linhas separadoras

Com o GridView genérico e classificável concluído, tudo o que resta é ser capaz de adicionar as linhas separadora no GridView antes de cada grupo classificado exclusivo. 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 gridView RowDataBound . Como discutimos no tutorial Formatação personalizada baseada em dados , esse manipulador de eventos é comumente 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 gridView Rows , na verdade, é somente leitura.

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

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

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 adicionando linhas separadores aos dados reais que estão sendo associados ao GridView e manipulando a coleção de controles gridView após sua associação - atacam o problema de forma diferente e merecem uma discussão.

Adicionando linhas ao limite de dados 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 separadoras 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 separadora à fonte de dados

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

Uso os registros separadores de termos entre aspas porque não há registro separador especial; em vez disso, devemos de alguma forma sinalizar 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 por ProductRows. Podemos sinalizar um registro como uma linha separadora definindo sua CategoryID propriedade -1 como (já que esse valor não podia 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 e SortDirection do SortExpression GridView
  3. Iterar pelo ProductsRows no ProductsDataTable, procurando onde estão as diferenças na coluna classificada
  4. Em cada limite de grupo, insira uma instância de registro ProductsRow separador na DataTable, uma que a CategoryID tenha definida -1 como (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, marcar cada DataRow e determinar se era uma linha separadora, uma cuja CategoryID configuração 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 GridView Sorting e acompanhar os SortExpression valores e SortDirection .

Manipulando a coleção de controles GridView após ela ter sido databound

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 GridView contém um Table objeto em sua raiz, um GridViewRow (que é derivado da TableRow classe ) para cada registro no DataSource associado ao GridView e um TableCell objeto em cada GridViewRow instância para cada campo de dados no DataSource.

Para adicionar linhas separadoras 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 no momento em que a página está sendo renderizada. Portanto, essa abordagem substitui o Page método da classe s Render , momento em que a hierarquia de controle final do GridView é atualizada para incluir as linhas separadoras necessárias. A Figura 4 ilustra esse processo.

Uma técnica alternativa manipula a hierarquia de controle gridView

Figura 4: Uma técnica alternativa manipula a hierarquia de controle 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 de blog do Teemu Keiski , Reproduzindo um Bit com o Agrupamento de Classificação gridView.

Etapa 3: Adicionar as linhas separadoras à hierarquia de controle 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 à 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 ponto mais recente possível no qual podemos fazer isso é o Page evento de classe s Render , 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 de chamar base.Render(writer), para que as linhas separadoras 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 tenha solicitado que os dados sejam classificados. Por padrão, o conteúdo do GridView não é classificado e, portanto, não precisamos inserir nenhum cabeçalho 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 na Sort primeira visita à página (mas não em postbacks subsequentes). Para fazer isso, adicione essa chamada no Page_Load manipulador de eventos em um if (!Page.IsPostBack) condicional. Consulte as informações do tutorial Paginação e Classificação de Dados do Relatório para obter mais informações sobre o Sort método .

Supondo que os dados foram classificados, nossa próxima tarefa é determinar por qual coluna os dados foram classificados e, em seguida, 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 não SortExpression terá sido definida. Portanto, só queremos adicionar as linhas do separador se essa propriedade tiver algum valor. Se isso acontecer, precisaremos determinar o índice da coluna pela qual os dados foram classificados. Isso é feito executando um loop pela coleção gridView, Columns pesquisando a coluna cuja SortExpression propriedade é igual à propriedade gridView s SortExpression . Além do índice da coluna s, também capturamos a HeaderText propriedade , que é usada ao exibir as linhas do separador.

Com o índice da coluna pelo qual os dados são classificados, a etapa final é enumerar as linhas do GridView. Para cada linha, precisamos determinar se o valor da coluna classificada é diferente do valor da coluna classificada 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 classificada da linha atual 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 classificada da 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. Veremos como considerar os campos alternativos do GridView em breve.

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

Observe que a linha separadora s lone TableCell é formatada de modo que abrange toda a largura do GridView, é formatada usando a SortHeaderRowStyle classe CSS e tem sua Text propriedade de modo que mostre o nome do grupo de classificação (como Categoria ) e 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 as configurações de estilo que lhe agradem; 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 de classificação 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 do grupo de classificação estão ausentes ao classificar por um CheckBoxField é porque o código atualmente usa apenas a TableCell propriedade s Text para determinar o valor da coluna classificada para cada linha. Para CheckBoxFields, a TableCell propriedade s Text é uma cadeia de caracteres vazia; em vez disso, o valor está disponível por meio de um controle Web CheckBox que reside na TableCell coleção s Controls .

Para lidar com tipos de campo diferentes de BoundFields, precisamos aumentar o código em que a currentValue variável é atribuída a marcar para a existência de uma CheckBox na TableCell coleção sControls. Em vez de usar currentValue = gvr.Cells[sortColumnIndex].Text, substitua esse 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 Controls coleção. Se houver, e o primeiro controle for um CheckBox, a currentValue variável será definida como Sim ou Não, dependendo da propriedade checkbox.Checked Caso contrário, o valor será obtido da TableCell propriedade s Text . Essa lógica pode ser replicada para lidar com 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 os CategoryIDcampos , SupplierIDou UnitPrice , 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 como o texto desejado ou adicionar uma instrução condicional no método Render ao atribuir o currentValue à propriedade da linha do Text separador.

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 separadora 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, marcar a entrada do blog Dicas e Truques de Classificação de Scott Guthrie ASP.NET 2.0 GridView.

Programação feliz!

Sobre o autor

Scott Mitchell, autor de sete livros do 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 contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.