Sie sind auf Seite 1von 18

Criando colunas

dinamicamente
no ASP.NET
DataGrid
por Marcos Santos e Alexandre
Santos

Este artigo discute Este artigo usa as seguintes tecnologias:
Controle ASP.NET DataGrid
Acesso a banco de dados
C#, SQL
Download:
fontesDataGridDinamico.zip
Chapu
DataGrid

Desde o lanamento do ASP.NET o DataGrid
um dos controles mais empregados,
principalmente pela sua fcil utilizao e
quantidade de recursos que auxiliam o
desenvolvedor a criar pginas robustas e
atrativas. Atravs do Visual Studio .NET fica
ainda mais inspirador o uso de DataGrid, dada
velocidade de desenvolvimento, j que em
poucos cliques, tm-se acesso aos Wizards que
configuram as colunas (Property Builder) e o
formato visual (Auto Format), restando apenas
atribuir o seu DataSource, podendo ser um
DataTable, Colees entre outros.
O objetivo deste artigo mostrar a formatao
e criao de colunas no DataGrid de forma
dinmica. criado um projeto ASP.NET que
efetua uma consulta de Produtos com suas
respectivas Categorias no banco de dados
Northwind do SQL Server. apresentado ao
usurio todos os Produtos agrupados por
Categorias, com subtotais. Para isto, utilizado
como DataSource um novo DataTable criado a
partir dos dados retornados na consulta.
Talvez voc esteja se perguntando: "Por que
criar colunas dinamicamente, se possvel cri-
las sem uma linha de cdigo e de forma quase
que instantnea utilizando o VS.NET?". Certo? A
resposta para tal pergunta pode ser melhor
analisada atravs do seguinte questionamento:
"Como fazer para que um mesmo DataGrid seja
parametrizado de forma tal, que em certas
situaes apresente X colunas e em outras
situaes apresente Y colunas?". Resposta: Criar
as colunas dinamicamente, podendo tambm
utilizar artifcios de orientao a objetos (OO) -
Herana e Polimorfismo.
Para aumentar a compreenso da resposta
apresentada, imagine que voc queira
centralizar seus relatrios em apenas uma
pgina .aspx com apenas um DataGrid e que
cada relatrio tenha consulta SQL diferente um
do outro. Uma forma vivel, atravs de OO
criar uma classe pai RelatorioPadrao com dois
mtodos: retornaColunas() e retornaDados() e
para cada relatrio a ser gerado, criar classes
filhas (exemplo RelatorioProduto) que iro
sobrescrever estes mtodos. Desta forma, na
pgina .aspx ser instanciada a classe desejada e
invocados os mtodos que retornam as colunas
do DataGrid e seu DataSource.
Neste artigo, para simplificar, no ser
modelado atravs de classes e subclasses. A
criao das colunas, formatao do DataGrid e
agrupamento com subtotais sero feitos
diretamente na classe da Interface (.aspx).
Projeto
Crie um projeto ASP.NET Web Application
chamado DataGridAgrupadoColunas usando
como linguagem o Visual C#, contendo um
formulrio chamado GridAgrupadoColunas.aspx.
Insira 3 controles, 1 DataGrid (id =
gridProdutoCategorias), 1 Button (id =
btnSelecionados ) e 1 ListBox (id =
lstSelecionados) - veja a Figura 1. Apenas
observe que so utilizadas tabelas HTML para
possibilitar uma melhor organizao visual.

Figura 1 - Layout de Interface sugerido.
Para agilizar o tempo de desenvolvimento,
selecione o DataGrid e formate a cor e fonte do
cabealho, atravs de suas propriedades
HeaderStyle. Em tempo de design no
necessrio fazer mais nada.
No projeto sugerido realizada uma consulta ao
Banco de Dados Northwind do SQL Server,
retornando a tabela Products com sua
respectiva Categories, como na Figura 2. Porm,
os produtos sero visualizados agrupados por
Categorias no DataGrid, incluindo subtotais e,
no final, um Total Geral. Para isto, via
programao, a partir da consulta original
criado um novo DataTable no modelo final.

Figura 2 - Query realizada no Northwind.
No DataGrid so criadas 5 colunas
dinamicamente:
Nome Tipo Obs.
Selecionar TemplateColumn TemplateColumnCheckBox
Descrio BoundColumn
Valor Unitrio BoundColumn
Quantidade Estoque BoundColumn
Valor Estoque BoundColumn
BoundColumns so colunas que se relacionam
com os campos do DataSource do DataGrid em
questo. TemplateColumns so colunas
complexas, que permitem a insero de diversos
outros controles, por exemplo um CheckBox.
Neste caso, a coluna "Selecionar" possuir um
CheckBox em todas as linhas, menos nos grupos
e subtotais, permitindo que o internauta informe
quais produtos deseja selecionar. Quando o
usurio clicar no boto "Mostra Selecionados",
os produtos selecionados sero adicionados ao
ListBox. Esta funcionalidade para explicitar que
mesmo sendo adicionados controles em tempo
de desenvolvimento (runtime), seus valores
permanecem entre PostBacks.
Inicio da pagina
Cdigo
Primeira parte a ser desenvolvida a criao das
colunas do DataGrid, para isto no mtodo OnInit
deve ser invocada a rotina "criaColunasGrid()". A
necessidade da chamada ser no OnInit ocorre
devido que neste evento os controles ainda no
so populados com seus dados pelo DataView,
ltimo estado entre PostBacks.
override protected void
OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
//dever ser invocado aqui p/
manter a renderizao
//das colunas, atravs de
PostBacks
this.criaColunasGrid();
}
Digite a rotina criaColunasGrid que cria as
colunas dinamicamente no DataGrid.
//Cria as colunas dinamicamente no
DataGrid
private void criaColunasGrid()
{
//Colunas no geradas
automaticamente

gridProdutoCategorias.AutoGenerateC
olumns = false;
//coluna Template que nos permite
adicionar o checkbox
TemplateColumn colSelecionar =
new TemplateColumn();
colSelecionar.HeaderText =
"Selecionar";
//informa o que o ItemTemplate
do tipo TemplateColumnCheckBox
colSelecionar.ItemTemplate = new
TemplateColumnCheckBox();

//coluna descrio
BoundColumn colDescricao = new
BoundColumn();
colDescricao.DataField =
"Descricao";
colDescricao.HeaderText =
"Descrio";

colDescricao.ItemStyle.HorizontalAl
ign =
System.Web.UI.WebControls.Horizonta
lAlign.Left;

//coluna valor unitrio
BoundColumn colValorUnitario =
new BoundColumn();
colValorUnitario.DataField =
"valorUnitario";
colValorUnitario.HeaderText =
"Valor Unitrio";
colValorUnitario.DataFormatString
= "{0:F2}";

colValorUnitario.ItemStyle.Horizont
alAlign =
System.Web.UI.WebControls.Horizonta
lAlign.Right;

//coluna Unidades no Estoque
BoundColumn colUnidadesEstoque =
new BoundColumn();
colUnidadesEstoque.DataField =
"unidadesEstoque";
colUnidadesEstoque.HeaderText =
"Quantidade Estoque";

colUnidadesEstoque.ItemStyle.Horizo
ntalAlign =
System.Web.UI.WebControls.Horizonta
lAlign.Right;

//coluna (valorUnitario *
unidadesEstoque)
BoundColumn colValorEstoque = new
BoundColumn();
colValorEstoque.DataField =
"valorEstoque";
colValorEstoque.HeaderText =
"Valor Estoque";
colValorEstoque.DataFormatString
= "{0:F2}";//duas casas decimais

colValorEstoque.ItemStyle.Horizonta
lAlign =
System.Web.UI.WebControls.Horizonta
lAlign.Right;

//adiciona colunas criadas no
grid

gridProdutoCategorias.Columns.Add(c
olSelecionar);

gridProdutoCategorias.Columns.Add(c
olDescricao);

gridProdutoCategorias.Columns.Add(c
olValorUnitario);

gridProdutoCategorias.Columns.Add(c
olUnidadesEstoque);

gridProdutoCategorias.Columns.Add(c
olValorEstoque);
}
Perceba que para as colunas BoundColumn
necessrio informar o campo referente ao seu
DataSource, atravs da propriedade DataField.
Possibilita-se tambm a formatao atravs de
outras propriedades, como HeaderText (nome
no cabealho), DataFormatString,
HorizontalAlign, entre outras.
Para as colunas TemplateColumn, a propriedade
ItemTemplate permite indicar quais controles
sero adicionados e visualizados. Isto possvel
indicando um objeto de uma classe que
implementa a interface ITemplate. Aqui ser um
CheckBox. Para isto, crie uma nova classe
TemplateColumnCheckBox, como segue:
//Define um ItemTemplate c/
CheckBox
public class TemplateColumnCheckBox
: System.Web.UI.ITemplate
{
public void
InstantiateIn(System.Web.UI.Control
container)
{
//adiciona um checkbox na
coluna template

System.Web.UI.WebControls.CheckBox
chRetorno = new
System.Web.UI.WebControls.CheckBox(
);
chRetorno.ID = "chSelecionado";


container.Controls.Add(chRetorno);
}
}
At aqui as colunas do DataGrid j esto sendo
criadas dinamicamente. O prximo passo
realizar a consulta ao banco de dados que ser
manipulada para gerar o DataTable no formato
final, o qual ser o DataSource do DataGrid. Para
isto, digite o mtodo
"retornaCategoriasProdutos()":
//Retorna DataTable que ser
navegado, para gerar o DataTable
agrupado (no formato final).
public DataTable
retornaCategoriasProdutos()
{
//faz a conexo com o banco de
dados e realiza a consulta com os
dados
//primrios que sero manipulados
p/ servir de fonte de dados ao grid
string sConexao = "Initial
Catalog=Northwind; Data
source=localhost; user id=sa;";

System.Text.StringBuilder sSql =
new System.Text.StringBuilder();
sSql.Append(" select
c.CategoryName as Categoria,
p.ProductName as Produto,
p.ProductId as IdProduto, ");
sSql.Append(" UnitPrice as
valorUnitario, ");
sSql.Append(" UnitsInStock as
unidadesEstoque ");
sSql.Append(" from categories c
join products p ");
sSql.Append(" on c.categoryid =
p.categoryid ");
sSql.Append(" order by
c.CategoryName, p.ProductName");

SqlConnection conexao = new
SqlConnection(sConexao);
conexao.Open();

SqlDataAdapter daConsulta = new
SqlDataAdapter(sSql.ToString(),cone
xao);
DataSet dsConsulta = new
DataSet();
daConsulta.Fill(dsConsulta);

return dsConsulta.Tables[0];
}
Cabe ressaltar que a conexo deve ser
configurada de acordo com a instalao do SQL
Server no servidor, podendo haver Password,
segurana integrada, etc. Ateno: No esquea
de adicionar o namespace System.Data.SqlClient
na lista de using.
Antes de criar o DataTable com os dados
agrupados, necessrio declarar alguns
atributos na lista de private, que sero utilizados
para controle de quebra de categoria,
totalizadores e o prprio DataTable
(dtDadosAgrupados).
//varivel que controla a troca de
categoria
private string deCategoriaAnterior
= "";

//variveis setadas com os valores
da linha do BD que est sendo
manipulada
private string deCategoriaAtual =
"";
private string deProdutoAtual = "";

private double valorAtual = 0;
private int unidadesAtual = 0;

//variveis Totalizadoras da
Categoria Atual
private double valorCategoriaAtual
= 0;
private double
valorEstoqueCategoriaAtual = 0;
private int
quantidadeCategoriaAtual = 0;

//variveis Totalizadoras do "Total
Geral"
private double valorTotal = 0;
private double valorEstoqueTotal =
0;
private int quantidadeTotal = 0;

//varivel p/ controlar a linha
atual
private int indiceLinha = 1;
//inicializado com 1
//DataTable que conter os dados
manipulados e servir de fonte de
dados p/ o grid
private DataTable dtDadosAgrupados;
//enum p/ associar um tipo p/ cada
linha. Utilizado p/ pintar cada
tipo de linha de uma cor.
private enum TipoLinha
{DescricaoCategoria,
DescricaoProduto, SubTotal, Total};
Digite o cdigo que instncia e cria as colunas
no DataTable que conter os dados agrupados,
pelo mtodo "criaDataTableDadosAgrupados()".
Atente para as colunas "Descrio" e "tipoLinha".
Para permitir que seja feito o agrupamento, a
coluna "Descricao" possuir as descries das
Categorias, Produtos, subtotais e Total Geral e
por sua vez a coluna "tipoLinha", informar ao
DataTable o tipo da linha conforme o enum
TipoLinha, declarado na lista de private.
//Cria DataTable que conter os
dados manipulados em forma de
subgrupos.
private void
criaDataTableDadosAgrupados()
{
this.dtDadosAgrupados = new
DataTable();

this.dtDadosAgrupados.Columns.Add("
Descricao",

System.Type.GetType("System.String"
));

this.dtDadosAgrupados.Columns.Add("
valorUnitario",

System.Type.GetType("System.Double"
));

this.dtDadosAgrupados.Columns.Add("
unidadesEstoque",

System.Type.GetType("System.Int32")
);

this.dtDadosAgrupados.Columns.Add("
valorEstoque",

System.Type.GetType("System.Double"
));

this.dtDadosAgrupados.Columns.Add("
tipoLinha",

System.Type.GetType("System.Int32")
);
}
Atravs do mtodo
"retornaCategoriasProdutos()" criado
anteriormente, obtm-se o DataTable com o
retorno do banco de dados, possibilitando o
preenchimento do DataTable
dtDadosAgrupados j no formato a ser
mostrado ao usurio, isto , com grupos de
Categorias, subtotais e Total Geral. Digite o
mtodo "populaDataTableDadosAgrupados",
que ento varre o retorno do banco de dados e
faz a validao de troca de categorias. Caso o
registro atual da iterao seja uma nova
categoria, so adicionadas linhas de subtotal (da
categoria anterior) e a linha de Descrio da
nova categoria. No final, adicionada a linha de
Total Geral. Observe que em cada iterao so
atualizados os Totalizadores dos subtotais e
Total Geral, sendo que na troca de categoria, os
subtotais so zerados.
//Navega no DataTable retornado no
SQL e popula o DataTable Agrupado
(no formato final)
private void
populaDataTableDadosAgrupados()
{
foreach (DataRow drRetornoBD in
this.retornaCategoriasProdutos().Ro
ws)
{
//salva os dados da linha atual
p/ serem
//manipulados nos outros
mtodos
deCategoriaAtual =
drRetornoBD["Categoria"].ToString()
;
deProdutoAtual =
drRetornoBD["Produto"].ToString();
valorAtual =
Convert.ToDouble(drRetornoBD["valor
Unitario"]);
unidadesAtual =
Convert.ToInt32(drRetornoBD["unidad
esEstoque"]);

//Valida troca de categoria...
if (this.deCategoriaAnterior !=
this.deCategoriaAtual)
{
//Insere subTotal da Categoria
anterior, porm somente se
//ndiceLinha != 1, pois neste
caso seria a primeira iterao no
for
if (this.indiceLinha != 1)

dtDadosAgrupados.Rows.Add(retornaLi
nhaSubTotal());

//adiciona linha c/ a descrio
da nova Categoria.

dtDadosAgrupados.Rows.Add(retornaLi
nhaCategoria());

//"true" p/ zerar os somatrios
desta categoria, pois mudou a
categoria
atualizaTotalizadores(true);

//atualiza deCategoriaAnterior
com a Atual
deCategoriaAnterior =
this.deCategoriaAtual;
}

//adiciona linha de Produto no
DataTable agrupado.

dtDadosAgrupados.Rows.Add(retornaLi
nhaProduto());

//Incrementa Totalizadores e
"false" p/ no
//zerar os somatrios desta
categoria.
atualizaTotalizadores(false);

indiceLinha++;
}//foreach

//adiciona subtotal da ltima
categoria

dtDadosAgrupados.Rows.Add(retornaLi
nhaSubTotal());
//adiciona Total Geral

dtDadosAgrupados.Rows.Add(retornaLi
nhaTotal());
}
A seguir, devem ser adicionados os mtodos
que retornam as linhas descritivas das
Categorias, Produtos, subtotais e Total Geral ao
DataTable dtDadosAgrupados:
//Retorna DataRow com a Descrio
da Categoria Atual
private DataRow
retornaLinhaCategoria()
{
//linha de subtitulo da
categoria.
DataRow drCategoria =
dtDadosAgrupados.NewRow();
drCategoria =
dtDadosAgrupados.NewRow();
drCategoria["Descricao"] = "
>>Categoria: "+

deCategoriaAtual;
drCategoria["tipoLinha"] =
(int)TipoLinha.DescricaoCategoria;
//fim linha de subtitulo da
categoria
return drCategoria;
}

//Retorna DataRow com Dados do
Produto Atual
private DataRow
retornaLinhaProduto()
{
//retorna a linha com os dados do
produto
DataRow drProduto =
dtDadosAgrupados.NewRow();
drProduto["Descricao"] =
deProdutoAtual;
drProduto["valorUnitario"] =
valorAtual;
drProduto["unidadesEstoque"] =
unidadesAtual;
drProduto["valorEstoque"] =
valorAtual *
unidadesAtual;
drProduto["tipoLinha"] =
(int)TipoLinha.DescricaoProduto;

return drProduto;
}


//Retorna DataRow com SubTotal da
Categoria
private DataRow
retornaLinhaSubTotal()
{
//retorna a linha de subtotal
aps cada categoria.
DataRow drSubTotal =
dtDadosAgrupados.NewRow();
drSubTotal =
dtDadosAgrupados.NewRow();
drSubTotal["Descricao"] = "Sub
Total";
drSubTotal["valorUnitario"] =
valorCategoriaAtual;
drSubTotal["unidadesEstoque"] =
quantidadeCategoriaAtual;
drSubTotal["valorEstoque"] =
valorEstoqueCategoriaAtual;
drSubTotal["tipoLinha"] =
(int)TipoLinha.SubTotal;
return drSubTotal;
}


//Retorna DataRow com Total Geral
private DataRow retornaLinhaTotal()
{
//retorna a linha final de Total
Geral.
DataRow drTotal =
dtDadosAgrupados.NewRow();

drTotal["Descricao"] = "Total
Geral";
drTotal["valorUnitario"] =
valorTotal;
drTotal["unidadesEstoque"] =
quantidadeTotal;
drTotal["valorEstoque"] =
valorEstoqueTotal;
drTotal["tipoLinha"] =
(int)TipoLinha.Total;
return drTotal;
}
Para finalizar a implementao do
preenchimento do DataTable agrupado
necessrio adicionar o mtodo
"atualizaTotalizadores", que incrementa ou zera
os totalizadores da categoria da iterao atual e
que tambm incrementa os totalizadores do
Total Geral.
//Atualiza Totalizadores
private void
atualizaTotalizadores(bool
_zeraTotCampanhaAtual)
{
if (_zeraTotCampanhaAtual)
{
//zera totalizadores da
Categoria Atual
this.valorCategoriaAtual = 0;
this.quantidadeCategoriaAtual
= 0;

this.valorEstoqueCategoriaAtual =
0;
}
else
{
//incrementa totalizadores da
Categoria Atual
valorCategoriaAtual +=
valorAtual;
quantidadeCategoriaAtual +=
unidadesAtual;
valorEstoqueCategoriaAtual +=
(valorAtual * unidadesAtual);

//incrementa totalizadores do
Total Geral
valorTotal += valorAtual;
quantidadeTotal +=
unidadesAtual;
valorEstoqueTotal +=
(valorAtual * unidadesAtual);
}
}
Preenchido o DataTable agrupado, altere o
evento Page_Load para que efetivamente faa a
chamada aos mtodos que criam e populam o
DataTable, alm de fornec-lo ao DataGrid
como seu DataSource. Todos os cdigos
contidos no (!Page.IsPostBack) sero executados
apenas a primeira vez em que pgina for
carregada.
//Executado toda vez que carrega a
pgina.
private void Page_Load(object
sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
//cria DataTable com os
DadosAgrupados
criaDataTableDadosAgrupados();
//popula DataTable Agrupado,
atravs dos dados provenientes
//da consulta SQL no Banco de
Dados

populaDataTableDadosAgrupados();

//seta a fonte de dados e
mostra os dados

gridProdutoCategorias.DataSource =
dtDadosAgrupados;

gridProdutoCategorias.DataBind();
}
//remove CheckBox entre PostBacks
removeCheckBox();
}
Para cada item que for populado ao DataGrid
ser criado um CheckBox, devido coluna
TemplateColumn criada. Porm, para as linhas
de Categorias, subtotais e TotalGeral, no
desejado mostr-lo ao usurio, justificando o
uso da rotina "removeCheckBox()" referenciada
no Page_Load().
//Remove CheckBox das linhas
Totalizadoras.
public void removeCheckBox()
{
//remove CheckBox nas linhas de
categoria,
//subtotais e Total Geral
foreach (DataGridItem item in
gridProdutoCategorias.Items)
{
if ((item.Cells[1].Text == "Sub
Total") ||
(item.Cells[1].Text ==
"Total Geral") ||

(item.Cells[1].Text.ToString().Inde
xOf(">>Categoria:") != -1) )
{

item.Cells[0].Controls.RemoveAt(0);
}
}
}
No momento que realizado o DataBind() do
DataGrid, automaticamente disparado o
evento ItemDataBound, que ocorre como ltima
oportunidade para se acessar o dado de um
item do grid, antes que seja exposto ao usurio.
Desta forma, o utilizaremos para colorir as linhas
totalizadoras. Para criar este evento, selecione o
gridProdutoCategorias e na janela de
propriedades d um duplo clique no evento
ItemDataBound.
//Evento que colore as linhas de
acordo com o seu Tipo.
private void
gridProdutoCategorias_ItemDataBound
(object sender,
System.Web.UI.WebControls.DataGridI
temEventArgs e)
{
if (e.Item.ItemType ==
ListItemType.Item ||
e.Item.ItemType ==
ListItemType.AlternatingItem)
{
//drvItem faz referencia a
linha do dataSource do grid do item
atual
DataRowView drvItem =
(DataRowView)e.Item.DataItem;
TipoLinha tipo =
(TipoLinha)(drvItem["tipoLinha"]);
//switch p/ colorir a linha de
acordo com o tipo de linha
switch (tipo)
{
case
TipoLinha.DescricaoCategoria :
{
e.Item.BackColor =
Color.Silver;
break;
}
case TipoLinha.SubTotal :
{
e.Item.BackColor =
Color.Gainsboro;
break;
}
case TipoLinha.Total :
{
e.Item.BackColor =
Color.Gray;
break;
}
}
} //fim if
}
Note que verificado o item atual do DataGrid
encontra-se atravs do DataRowView, o valor da
coluna "tipoLinha" do dtDadosAgrupados.
Atravs deste valor e do enum TipoLinha,
colore-se o item do DataGrid.
Com o objetivo de mostrar que possvel
capturar dados do DataGrid entre PostBacks,
mesmo que suas colunas tenham sido criadas
dinamicamente, d um duplo clique no boto
btnSelecionados e digite o seguinte cdigo, que
listar no ListBox todos os itens que o CheckBox
estiver selecionado:
//Mostra Todos os itens
selecionados dos CheckBoxs da
coluna Template
private void
btnSelecionados_Click(object
sender, System.EventArgs e)
{
lstSelecionados.Visible = true;
lstSelecionados.Items.Clear();
lstSelecionados.Items.Add("Items
Selecionados:");
foreach (DataGridItem item in
gridProdutoCategorias.Items)
{
CheckBox chSelecionado =
(CheckBox)item.FindControl("chSelec
ionado");
if ((chSelecionado != null) &&
(chSelecionado.Checked))

lstSelecionados.Items.Add(item.Cell
s[1].Text);
}
}
Salve o projeto e execute (CTRL + F5) para ver o
resultado do gridProdutoCategorias montado
dinamicamente (Figura 3). Experimente
selecionar alguns CheckBox e clique no boto
"Mostrar Selecionados" e veja como os itens
permanecem selecionados e tambm listados
no ListBox.

Figura 3 - Verso final da pgina sendo
executada.
Inicio da pagina
Concluso
A combinao entre DataGrid e DataTable
permite ao desenvolvedor manipular dados e
mostr-los aos usurios de uma forma muito
dinmica e eficiente. A criao de colunas
dinamicamente viabilizam a soluo de
problemas quanto a formatao do DataGrid,
aumentando em muito sua aplicabilidade, como
por exemplo, um sistema de Gerao de
Relatrios.
Marcos Santos (marcososantos@yahoo.com) e
Alexandre Santos (alexandrecpd@hotmail.com)
so graduados em Cincias da Computao na
UFSC, trabalham como Analistas de Sistemas na
Softway Contact Center e com a plataforma .NET
desde a verso Beta.
Inicio da pagina

Das könnte Ihnen auch gefallen