Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Scott Mitchell
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 de usuário de classificação.
Introdução
Ao exibir uma longa lista de dados classificados onde 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 NULL
opção). Considere o caso de um usuário que está interessado em examinar os produtos que se enquadram na categoria de frutos do mar. A partir de 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. Depois de classificar por categoria, o usuário precisa caçar a lista, procurando onde os produtos agrupados com frutos do mar começam e terminam. Como os resultados são ordenados alfabeticamente pelo nome da categoria, encontrar os produtos de frutos do mar não é difícil, mas ainda requer uma varredura cuidadosa da lista de itens na grade.
Para ajudar a destacar os limites entre grupos classificados, muitos sites empregam uma interface de 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.
Figura 1: Cada grupo de categorias está claramente identificado (Clique para visualizar a imagem em tamanho real)
Neste tutorial, veremos como criar essa interface de 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 classificável que lista os produtos. Comece por abrir a página CustomSortingUI.aspx
na pasta PagingAndSorting
. Adicione um GridView à página, defina sua ID
propriedade como ProductList
, e associe-o a um novo ObjectDataSource. Configure o ObjectDataSource para usar o ProductsBLL
método s de GetProducts()
classe para selecionar registros.
Em seguida, configure o GridView de modo que ele contenha apenas o ProductName
, CategoryName
, SupplierName
e UnitPrice
BoundFields e o Discontinued CheckBoxField. Por fim, configure o GridView para dar suporte à ordenação marcando a caixa de seleção Ativar Ordenação na etiqueta inteligente do GridView (ou definindo sua propriedade AllowSorting
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.
Figura 2: Os dados do Sortable GridView são ordenados por categoria (Clique para visualizar 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 é poder adicionar as linhas separadoras no GridView antes de cada grupo classificado exclusivo. Mas como essas linhas podem ser injetadas no GridView? Essencialmente, precisamos iterar através das linhas do GridView, determinar onde as diferenças ocorrem entre os valores na coluna classificada e, logo, 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 com base em dados , esse manipulador de eventos é comumente usado ao aplicar 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 a partir desse manipulador de eventos. A coleção GridView Rows
, na verdade, é somente leitura.
Para adicionar linhas adicionais ao GridView, temos três opções:
- Adicione essas linhas separadoras de metadados aos dados reais que estão vinculados ao GridView
- Depois que o GridView tiver sido vinculado aos dados, adicione instâncias adicionais
TableRow
à coleção de controle de GridView - Crie um controle de servidor personalizado que estende o controle GridView e substitui os métodos responsáveis pela construção da estrutura de 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 das profundezas do funcionamento interno do GridView. Portanto, não consideraremos essa opção para este tutorial.
As outras duas opções que adicionam linhas separadoras aos dados reais que estão sendo vinculados ao GridView e manipulam a coleção de controle do GridView depois que ele foi vinculado - atacam o problema de forma diferente e merecem uma discussão.
Adicionando linhas aos dados vinculados ao GridView
Quando o GridView é vinculado 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 vinculá-la ao GridView. A Figura 3 ilustra este conceito.
Figura 3: Uma técnica envolve a adição de linhas separadoras à fonte de dados
Eu uso o termo registros separadores 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, vinculamos uma ProductsDataTable
instância ao GridView, que é composto por ProductRows
. Podemos sinalizar um registro como uma linha separadora definindo sua CategoryID
propriedade como -1
(já que tal valor não poderia existir normalmente).
Para utilizar esta técnica, precisamos executar as seguintes etapas:
- Recuperar programaticamente os dados para vincular ao GridView (uma
ProductsDataTable
instância) - Classificar os dados com base nas propriedades
SortExpression
eSortDirection
do GridView. - Itere através do
ProductsRows
noProductsDataTable
, procurando onde estão as diferenças na coluna ordenada - Em cada limite de grupo, injete uma instância de registro
ProductsRow
separador na DataTable, uma que tenha o seuCategoryID
definida como-1
(ou qualquer designação que tenha sido decidida para marcar um registro como um registro separador) - Depois de injetar as linhas separadoras, vincule programaticamente os dados ao GridView
Além dessas cinco etapas, também precisamos fornecer um manipulador de eventos para o evento GridView RowDataBound
. Aqui, verificamos cada DataRow
e determinamos se era uma linha separadora, cuja configuração CategoryID
era -1
. Em caso afirmativo, provavelmente queremos ajustar a sua formatação ou o texto apresentado na(s) célula(s).
Usar esta técnica para injetar os limites do grupo de classificação requer um pouco mais de trabalho do que o descrito acima, pois precisa também de fornecer um handler de eventos para o evento de Sorting
do GridView e acompanhar os valores de SortExpression
e de SortDirection
.
Manipulando a coleção de controle de GridView depois que ela foi vinculada a dados
Em vez de enviar mensagens aos dados antes de vinculá-los ao GridView, podemos adicionar as linhas separadoras depois que os dados tiverem sido vinculados ao GridView. O processo de vinculação de dados constrói a hierarquia de controle de GridView, que na realidade é simplesmente uma Table
instância composta por uma coleção de linhas, cada uma das quais é composta por uma coleção de células. Especificamente, a coleção de controle GridView s contém um Table
objeto em sua raiz, um GridViewRow
(que é derivado da TableRow
classe) para cada registro no DataSource
vinculado 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 método da classe Page
Render
, no qual a hierarquia de controle final de GridView é atualizada para incluir as linhas de separação necessárias. A Figura 4 ilustra este processo.
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 é baseado no exemplo fornecido na entrada do blog de Teemu Keiski , Playing a Bit with GridView Sort Grouping.
Etapa 3: Adicionando as linhas separadoras à 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 à 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 processada em HTML. O último ponto possível no qual podemos realizar 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 será renderizado na página, gerando a marcação com base na sua hierarquia de controle. Portanto, é imperativo que ambos chamemos base.Render(writer)
, para que a página seja renderizada e que manipulemos a hierarquia de controle de GridView antes de chamar base.Render(writer)
, para que as linhas separadoras tenham sido adicionadas à hierarquia de controle de GridView antes de ser renderizada.
Para injetar os cabeçalhos do grupo de classificação, primeiro precisamos garantir que o usuário solicitou que os dados sejam classificados. Por padrão, o conteúdo de 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 s GridView Sort
na primeira visita à página (mas não nos postbacks subsequentes). Para fazer isso, adicione essa chamada no manipulador de eventos dentro de um Page_Load
condicional if (!Page.IsPostBack)
. Consulte as informações do tutorial Paging and Sorting Report Data para obter mais informações sobre o Sort
método.
Supondo que os dados tenham sido classificados, nossa próxima tarefa é determinar por qual coluna os dados foram classificados e, em seguida, verificar as linhas procurando 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 ordenado, a propriedade SortExpression
s do GridView não terá sido definida. Portanto, só queremos adicionar as linhas separadoras se essa propriedade tiver algum valor. Se isso acontecer, precisaremos determinar o índice da coluna pela qual os dados foram classificados. Isso é feito percorrendo a coleção Columns
do GridView, procurando a coluna cuja propriedade SortExpression
é igual à propriedade SortExpression
do GridView. Além do índice da coluna, também pegamos 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 s da coluna classificada difere do valor s da coluna classificada da linha anterior. Em caso afirmativo, 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 de GridView e criando uma variável de cadeia de caracteres chamada lastValue
.
lastValue
é usado para comparar o valor da coluna ordenada da linha atual com o valor da linha anterior. Em seguida, a coleção s Rows
GridView é enumerada e, para cada linha, o valor da coluna classificada é armazenado na currentValue
variável.
Observação
Para determinar o valor da coluna ordenada da linha específica, utilizo a propriedade da célula Text
. Isso funciona bem para BoundFields, mas não funcionará como desejado para TemplateFields, CheckBoxFields e assim por diante. Veremos como contabilizar campos GridView alternativos em breve.
As currentValue
variáveis e lastValue
são então comparadas. Se eles diferirem, precisamos adicionar uma nova linha separadora à hierarquia de controle. Isso é realizado determinando o índice do GridViewRow
na coleção de Table
do objeto Rows
, criando novas instâncias de GridViewRow
e TableCell
, e, em seguida, adicionando o TableCell
e o GridViewRow
à hierarquia de controle.
Observe que a linha separadora sozinha TableCell
é formatada de tal forma que cobre toda a largura do GridView, formatada usando a classe CSS SortHeaderRowStyle
e possui sua propriedade Text
tal que mostra o nome do grupo de ordenação (como Categoria) e o valor do grupo (como Bebidas). Finalmente, 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. Sinta-se à vontade para usar quaisquer configurações de estilo que lhe agradam; Eu 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 (veja 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 em nenhum lugar (consulte a Figura 6).
Figura 5: A interface de classificação inclui cabeçalhos de grupo de classificação ao classificar por BoundFields (Clique para visualizar a imagem em tamanho real)
Figura 6: Os cabeçalhos do grupo de classificação estão ausentes ao classificar um CheckBoxField (Clique para visualizar a imagem em tamanho real)
A razão pela 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 os CheckBoxFields, a TableCell
s Text
propriedade é uma Text
cadeia de caracteres vazia; em vez disso, o valor é disponibilizado através de um controle Web do CheckBox que reside dentro da coleção Controls
s .
Para lidar com tipos de campo diferentes de BoundFields, precisamos modificar o código onde a variável currentValue
é atribuída para verificar se existe uma CheckBox na coleção TableCell
Controls
. 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 para a linha atual para determinar se há algum controle na Controls
coleção. Se houver, e o primeiro controlo for uma CheckBox, a variável currentValue
será definida como Sim ou Não, dependendo da propriedade do CheckBox Checked
. Caso contrário, o valor é retirado da propriedade TableCell
s Text
. Essa lógica pode ser replicada para manipular a classificação de quaisquer TemplateFields que possam existir no GridView.
Com a adição de código acima, os cabeçalhos do grupo de classificação agora estão presentes ao realizar a ordenação por Discontinued CheckBoxField (consulte a Figura 7).
Figura 7: Os cabeçalhos do grupo de classificação agora estão presentes ao classificar um CheckBoxField (Clique para visualizar a imagem em tamanho real)
Observação
Se tiver produtos com valores NULL
de banco de dados para os campos CategoryID
, SupplierID
ou UnitPrice
, esses valores aparecerão como cadeias de caracteres vazias no GridView por padrão, o que significa que o texto da linha separadora para esses produtos com valores NULL
será lido como Categoria: (ou seja, não há nome após Categoria: como em Categoria: Bebidas). Se desejar que um valor seja exibido aqui, você pode definir a propriedade BoundFields NullDisplayText
como o texto que deseja exibir ou pode adicionar uma instrução condicional no método Render ao atribuir a currentValue
propriedade s à linha separadoraText
.
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 mais facilmente os grupos distintos e os limites desses grupos. Para obter exemplos adicionais de interfaces de classificação personalizadas, confira a entrada de blog A Few ASP.NET 2.0 GridView Sorting Tips and Tricks de Scott Guthrie.
Feliz Programação!
Sobre o Autor
Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.