Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você 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 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.
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.
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.
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:
- Recuperar programaticamente os dados a serem associados ao GridView (uma
ProductsDataTable
instância) - Classificar os dados com base nas propriedades
SortExpression
eSortDirection
do GridView. - Iterar através do
ProductsRows
noProductsDataTable
, procurando onde estão as diferenças na coluna ordenada - Em cada limite de grupo, insira uma instância de registro
ProductsRow
separador na DataTable, em que seuCategoryID
esteja definido para-1
(ou qualquer designação que tenha sido decidida para marcar um registro como um registro separador) - 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.
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).
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)
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 TableCell
Text
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 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 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).
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
, SupplierID
ou 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.