Formatação Avançada de DataGrid com ItemDataBound
Estava querendo produzir DataGrids com as mesmas funcionalidades de relatórios impressos e a coisa toda se parecia com alguns sistemas de gerenciamento de informações muito caros que foram substituídos com o .NET. O produto final deveria focar a atenção dos usuários nos dados desejados, sem as complicações de campos extras e outros detalhes. Este artigo se propõe a introduzir o uso de alguns truques simples para incrementar a aparência dos DataGrids.
Veremos então como fazer para:
· Criar agrupamentos de colunas
· Realçar subtotais e totais gerais
· Realçar células individuais com base em valores de dados
Dados SQL e Banco de dados
Foram usados o Northwind e o SQL Server DB para produzir os dados para este artigo, com um subtotal para cada categoria de produto e um total geral no final. O código funcionará para qualquer fonte de dados. As stored procedures SQL foram também incluídas para aqueles que estejam interessados em recriar exatamente o que é mostrado aqui.
Quase toda a lógica de negócio foi colocada no lado do banco de dados, usando o DataGrid apenas para fins de exibição, portanto, toda a ordenação e os totais são feitos aqui.
CREATE PROCEDURE usp_sales_by_cate AS
create table #temp ( Sorty int, CategoryName varchar(50),
ProductName varchar(50), ProductSales real)
-- Get Base Sales
INSERT INTO #temp
SELECT 0, dbo.Categories.CategoryName, dbo.Products.ProductName,
SUM(dbo.[Order Details Extended].ExtendedPrice)
AS ProductSales
FROM dbo.Categories INNER JOIN
dbo.Products INNER JOIN
dbo.Orders INNER JOIN
dbo.[Order Details Extended] ON dbo.Orders.OrderID =
dbo.[Order Details Extended].OrderID ON
dbo.Products.ProductID =
dbo.[Order Details Extended].ProductID ON dbo.Categories.CategoryID =
dbo.Products.CategoryID
WHERE (dbo.Orders.OrderDate BETWEEN '19970101' e '19971231')
GROUP BY dbo.Categories.CategoryName, dbo.Products.ProductName
ORDER BY dbo.Categories.CategoryName
-- Build SubTotal
INSERT INTO #temp
SELECT 1 , CategoryName, 'SubTotal', sum( ProductSales)
from #temp
group by CategoryName
-- Build Grand Total
INSERT INTO #temp
SELECT 2 , 'XXXXX', 'Grand Total', sum( ProductSales)
from #temp
Where sorty = 0
-- Display Values
SELECT CategoryName, ProductName, ProductSales from #temp
order by CategoryName, Sorty
Criando Agrupamentos
Não queremos que as categorias sejam exibidas sempre na primeira coluna, porém apenas quando mudarem, o que dará um aspecto limpo e agradável ao DataGrid, permitindo aos usuários achar os itens rápida e facilmente.
Primeiro, criamos uma variável pública que funcionará para a página inteira e, o mais importantemente, lembrará algo a cada vez que o <ITEMDATABOUND> rodar para cada linha do DataGrid. O Page Load, será também pré-configurado (ocorreram algumas situações estranhas em que isto não aconteceu).
public class WebForm1 : System.Web.UI.Page
{
public string LastColumn;
..
..
private void Page_Load(object sender, System.EventArgs e)
{
// Configurar LastColumn para branco
if (!IsPostBack) LastColumn = "";
A seguir, usamos a variável <LASTCOLUMN> para localizar as alterações nos dados que irão para o DataGrid. Se o valor da célula de cell[0] não for alterado, o texto da mesma será apagado e a borda será removida:
e.Item.Cells[0].Style.Add("BORDER", "none").
Para ignorar o cabeçalho (header) do DataGrid:
if( ( e.Item.ItemType.ToString()!= "Header"))
{.....
Isto também pode ser utilizado para os itens, itens alternativos e rodapés (footers) em um DataGrid, para especializar o código.
A seguir, o código completo para agrupar a primeira coluna do DataGrid:
private void DataGrid1_ItemDataBound(object sender,
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
//Obter o texto da coluna 0 atual
string CurrentColumn = e.Item.Cells[0].Text;
// Pular Headers
if( ( e.Item.ItemType.ToString()!= "Header"))
{
// Houve alguma alteração
if (CurrentColumn == LastColumn)
{
// Sem alteração na Coluna 0
//limpar e remover a borda
e.Item.Cells[0].Text = "";
e.Item.Cells[0].Style.Add("BORDER", "none");
}
else
{
// esta eh a primeira da serie
// atribuir LastColumn para a coluna atual
LastColumn = CurrentColumn;
// Adicionar uma cor de fundo para Cell[0]
e.Item.Cells[0].BackColor =
System.Drawing.Color.WhiteSmoke;
}
}
Realçando Subtotais e Totais Gerais
Isto é bastante simples, caso o texto da célula for "Sub Total" / "Grand Total", então configuramos a fonte, o tipo, as cores, etc.
// Verificar se eh um SubTotal
string MyCol2 = e.Item.Cells[1].Text;
if (MyCol2 == "SubTotal")
{
e.Item.Font.Bold = true;
e.Item.BackColor = Color.DimGray;
e.Item.ForeColor = Color.White;
// limpar Column 0
e.Item.Cells[0].Text = "";
e.Item.Cells[0].Style.Add("BORDER", "none");
e.Item.Cells[0].BackColor = Color.Transparent;
}
if (MyCol2 == "Grand Total")
{
e.Item.Font.Bold = true;
e.Item.BackColor = Color.Red;
e.Item.ForeColor = Color.White;
e.Item.Cells[0].Style.Add("BORDER", "none");
e.Item.Cells[0].BackColor = Color.Transparent;
}
Realçando Células Individuais
Este recurso é amplamente utilizado para exibir valores negativos em vermelho (do modo como se faz em contabilidade). Descobrimos que a conversão de string para número, pode produzir alguns erros estranhos, por isso, geralmente envelopamos as conversões em um try/catch para ignorar qualquer erro que possa surgir.
// Destacar itens caso as vendas estejam abaixo de 5000
string MyStr = e.Item.Cells[2].Text;
try
{
double MyValue = double.Parse(MyStr);
if (MyValue < 5000 )
{
e.Item.Cells[2].ForeColor = Color.Red;
e.Item.Cells[2].Font.Bold = true;
}
}
catch(Exception)
{
// numero invalido
}