Desenvolvimento - C#
Usando a DataGrid WPF
Uma das críticas ao WPF era o fato de não ter disponível uma DataGrid, para poder mostrar os dados em formato de tabela. A Microsoft lançou uma DataGrid para WPF que possui muitos recursos, permitindo fazer visuais bastante diferentes. Este artigo irá apresentar as características desta DataGrid, mostrando como usá-la.
por Bruno SonninoIntrodução
Uma das críticas ao WPF era o fato de não ter disponível uma DataGrid, para poder mostrar os dados em formato de tabela. Sem dúvida, poderíamos usar a DataGrid WinForms usando os componentes de interoperabilidade entre WPF e WinForms, mas esta seria uma solução muito aquém do esperado, pois não poderíamos aplicar nosso estilo de apresentação, na DataGrid. Outra solução seria usar a ListBox ou a ListView com um template de dados que permitisse a edição, mas isto ainda não permitia a flexibilidade que uma DataGrid permite.
Finalmente, a Microsoft ouviu os pedidos e disponibilizou uma DataGrid para WPF. Ela pode ser baixada (com o código fonte) junto com o WPF toolkit em http://www.codeplex.com/wpf . O WPF Toolkit, além da DataGrid, contém um DateTimePicker, um Calendário, temas para WPF e o VisualStateManager, que permite criar estados para um componente e transições entre os estados: por exemplo, o botão possui o estado MouseOver e o estado Pressed. Podemos criar estilos para os botões nestes estados e definir animações para as transições entre eles. Isto faz que o WPF fique semelhante ao Silverlight, que usa o VisualStateManager para estilizar os componentes.
Uma vez baixado, o toolkit, você deve instalar o arquivo msi que está incluído. Isto instala as referências para os novos assemblies.
Usando a DataGrid
Criaremos agora um projeto que lê dados de um banco de dados usando LINQ to SQL e mostra os dados na DataGrid. Crie um novo projeto WPF. No projeto adicione um novo LINQ to SQL classes e chame-o de NorthwindModel.dbml. No Server Explorer, adicione uma nova conexão de dados, usando SqlServer File e apontando para o lugar onde está o arquivo Northwind.mdf. Ao confirmar, a conexão deve aparecer no Server Explorer. Abra o ramo tables da conexão e arraste as tabelas Customers e Orders para o modelo. As tabelas e sua relação são adicionadas ao modelo. Agora iremos criar a nossa interface de usuário.
Para usar a DataGrid, devemos adicionar uma nova referêcia ao projeto. Dê um clique com o botão direito do mouse no Project Explorer e selecione a opção Add Reference e adicione o assembly WPFToolkit. Uma vez adicionada a referência, precisamos adicionar o namespace no código xaml. Isto é feito colocando a seguinte linha na declaração da janela, após os demais namespaces:
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;
assembly=WPFToolkit"
Em seguida, podemos colocar um componente DataGrid na janela, dentro do component Grid de layout:
<dg:DataGrid x:Name="dataGrid" AutoGenerateColumns="True" />
Estamos usando a propriedade AutoGenerateColumns, configurando-a para True, de modo que a DataGrid crie colunas para os nossos dados automaticamente. No construtor da janela, coloque o código que carrega os dados para a DataGrid:
public Window1()
{
InitializeComponent();
var dc = new NorthwindModelDataContext();
dataGrid.ItemsSource = dc.Customers.ToList();
}
Aqui criamos o contexto de dados e atribuímos a coleção Customers à lista de dados da DataGrid. Isto é o suficiente para alimentarmos os dados da DataGrid, como mostra a Figura 1.
Figura 1 – DataGrid com dados da tabela de clientes
Esta DataGrid, apesar de simples, tem uma série de características interessantes:
· Classifica os dados clicando na coluna, ou mesmo classificar por múltiplas colunas teclando Shift e clicando nas colunas desejadas
· Redimensiona as colunas individualmente
· Move as colunas de lugar
· Permite inserir dados
· Remove um dado quando teclamos Del
A inserção e deleção não são persistidas no banco de dados. Como usamos o método ToList() para alimentar a DataGrid, temos apenas uma coleção em memória, desligada do banco de dados. Para que os dados fossem persistidos, precisaríamos fazer algo como:
dataGrid.ItemsSource = dc.Customers;
Mas neste caso, perdemos a capacidade de classificar os dados, pois o LINQ to SQL traz os dados como Table<T> e isso faz que a classificação não seja suportada nesta versão da DataGrid. Se você quiser usar a classificação e persistir os dados, deve usar outro meio de acesso a dados, como o Dataset, ou criar um mecanismo que capture as mudanças na coleção e passe-as ao LINQ to SQL para que sejam persistidas, ou mesmo criar uma camada de acesso a dados que faça toda a atualização das modificações, porém isto está além do escopo deste artigo.
Mudando o estilo das linhas da DataGrid
A DataGrid, como todos os controles WPF, tem várias opções de personalização. Podemos mudar as cores e fontes das linhas e cabeçalhos, inclusive cores diferentes para linhas alternadas, criando uma tabela “zebrada”. A maneira mais fácil de fazer isso é usar as propriedades AlternationCount, que indica a quantas linhas a cor deve ser trocada, RowBackground e AlternatingRowBackground, que indicam as cores das linhas. Por exemplo, o código abaixo permite gerar a DataGrid conforme a Figura 2:
<dg:DataGrid x:Name="dataGrid" AutoGenerateColumns="True"
AlternationCount="2" RowBackground="Beige"
AlternatingRowBackground="LightBlue">
Figura 2 – DataGrid “zebrada”
Podemos também mudar o estilo do cabeçalho, criando um novo estilo. Inicialmente, criamos o novo estilo na seção Resources da janela:
<Window.Resources>
<Style x:Key="columnHeaderStyle"
TargetType="Primitives:DataGridColumnHeader">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0"
EndPoint="0.5,1" >
<LinearGradientBrush.GradientStops>
<GradientStop Color="Navy" Offset="0" />
<GradientStop Color="LightBlue" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="White" />
</Style>
</Window.Resources>
Em seguida, aplicamos este estilo à propriedade ColumnHeaderStyle:
<dg:DataGrid x:Name="dataGrid" AutoGenerateColumns="True"
AlternationCount="2" RowBackground="Beige"
AlternatingRowBackground="LightBlue"
ColumnHeaderStyle="{StaticResource columnHeaderStyle}" />
Assim, obtemos uma DataGrid como a da Figura 3.
Figura 3 – DataGrid com o cabeçalho customizado
Podemos fazer mudanças mais radicais nas linhas, mudando o seu estilo. Podemos criar um novo estilo para as linhas:
<Style x:Key="rowStyle" TargetType="dg:DataGridRow">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="10" />
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="#DDDDDD" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#BBBBBB" />
</Trigger>
</Style.Triggers>
</Style>
Com este estilo, mudamos o tipo de letra para Verdana e o tamanho para 10. Criamos triggers para as linhas alternadas e para quando o mouse está sobre uma linha. Precisamos tirar as propriedades RowBackground e AlternatingRowBackground da declaração da DataGrid e aplicar este estilo nas linhas para que ele tenha efeito:
<dg:DataGrid x:Name="dataGrid" AutoGenerateColumns="True"
AlternationCount="2"
ColumnHeaderStyle="{StaticResource columnHeaderStyle}"
RowStyle="{StaticResource rowStyle}"/>
A figura 4 mostra a DataGrid com este estilo aplicado.
Figura 4 – DataGrid com estilo aplicado
Estas não são as únicas personalizações que podem ser feitas. Podemos eliminar as linhas horizontais ou verticais entre as células, mudando a propriedade GridLinesVisiblility. Mudando para None, eliminamos as linhas completamente. Podemos usar ainda Horizontal, Vertical ou All, mostrando assim as linhas horizontais, verticais ou todas. Se não quiser eliminar completamente as linhas, pode mudar a maneira de apresentá-las, usando as propriedades HorizontalGridLinesBrush e VerticalGridLinesBrush. Por exemplo, fazendo
HorizontalGridLinesBrush="#DDDDDD"
Temos linhas horizontais cinza claro. Podemos também mudar a visibilidade dos cabeçalhos, mudando a propriedade HeadersVisibility. All mostra todos os cabeçalhos, Column mostra apenas os das colunas, Row mostra apenas os das linhas e None não mostra nenhum.
Por exemplo, se quisesse mostrar o código de cliente nos cabeçalhos das linhas, deveria configurar a propriedade HeadersVisibility para All e criar um estilo para o cabeçalho:
<Style x:Key="rowHeaderStyle"
TargetType="Primitives:DataGridRowHeader">
<Setter Property="Content" Value="{Binding Path=CustomerID}" />
</Style>
Então, basta aplicá-lo à DataGrid:
<dg:DataGrid x:Name="dataGrid" AutoGenerateColumns="True"
AlternationCount="2"
ColumnHeaderStyle="{StaticResource columnHeaderStyle}"
RowStyle="{StaticResource rowStyle}"
HeadersVisibility="All"
RowHeaderStyle="{StaticResource rowHeaderStyle}"
HorizontalGridLinesBrush="#DDDDDD"
VerticalGridLinesBrush="#DDDDDD"/>
A Figura 5 mostra o resultado desta mudança.
Figura 5 – DataGrid com cabaçalhos de linhas
Podemos ainda mudar os tamanhos das colunas, alterando a propriedade ColumnWidth. Se colocamos o valor SizeToCells, as colunas tomam o tamanho das células. SizeToHeader faz com que as colunas tenham o tamanho do cabeçalho. Se colocarmos um número, as colunas tem o tamanho em pixels e, usando “*” fazemos que as colunas se redimensionem proporcionalmente ao tamanho da janela.
Selecionando as colunas a serem apresentadas
Até agora, usamos a propriedade AutoGenerateColumns para que as colunas sejam geradas automaticamente, mas nem sempre isso é o que queremos. Por exemplo, em nossa grid temos o código do cliente tanto no cabeçalho quanto na tabela, alem de muitas colunas que não queremos mostrar.
Para configurar as colunas apresentadas , devemos adicionar uma coleção de colunas à DataGrid e adicionar as colunas que queremos mostrar:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="Nome"
Binding="{Binding Path=CompanyName}" />
<dg:DataGridTextColumn Header="Endereço"
Binding="{Binding Path=Address}" />
<dg:DataGridTextColumn Header="Cidade"
Binding="{Binding Path=City}" />
<dg:DataGridTextColumn Header="Região"
Binding="{Binding Path=Region}" />
<dg:DataGridTextColumn Header="País"
Binding="{Binding Path=Country}" />
<dg:DataGridTextColumn Header="Telefone"
Binding="{Binding Path=Phone}" />
</dg:DataGrid.Columns>
Ao executar o programa, temos algo como a Figura 6.
Figura 6 – Grid com as colunas selecionadas
Estamos adicionando colunas do tipo DataGridTextColumn, que permitem colocar texto nas colunas. Este não é o único tipo de colunas disponível. Podemos também adicionar colunas de outros tipos:
- Binding Path=Country,
Mode=TwoWay}">
<System:String>Argentina</System:String>
<System:String>Austria</System:String>
<System:String>Belgium</System:String>
<System:String>Brazil</System:String>
<System:String>Canada</System:String>
<System:String>Denmark</System:String>
<System:String>Switzerland</System:String>
<System:String>Germany</System:String>
<System:String>Spain</System:String>
<System:String>Finland</System:String>
<System:String>France</System:String>
<System:String>UK</System:String>
<System:String>Ireland</System:String>
<System:String>Italy</System:String>
<System:String>Mexico</System:String>
<System:String>Norway</System:String>
<System:String>Poland</System:String>
<System:String>Portugal</System:String>
<System:String>Sweden</System:String>
<System:String>USA</System:String>
<System:String>Venezuela</System:String>
</ComboBox>
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>
Para apresentar a célula, usamos um StackPanel com uma imagem com a bandeira e o nome do país. As bandeiras são arquivos png que podem ser baixados em http://www.famfamfam.com/lab/icons/flags/ e foram colocados no diretório Flags. Como só temos um nome de país, devemos fazer um conversor para transformar isso num BitmapImage, para configurar a propriedade Source.
Para criar este conversor, adicione ao projeto uma nova classe e dê o nome de CountryToFlagConverter. Coloque o seguinte código na classe:
class CountryToFlagConverter : IValueConverter
{
private Dictionary<string, string> countries =
new Dictionary<string, string>()
{
{"Argentina","AR"},
{"Austria","AT"},
{"Belgium","BE"},
{"Brazil","BR"},
{"Canada","CA"},
{"Denmark","DK"},
{"Switzerland","CH"},
{"Germany","DE"},
{"Spain","ES"},
{"Finland","FI"},
{"France","FR"},
{"UK","UK"},
{"Ireland","IE"},
{"Italy","IT"},
{"Mexico","MX"},
{"Norway","NO"},
{"Poland","PL"},
{"Portugal","PT"},
{"Sweden","SE"},
{"USA","US"},
{"Venezuela","VE"}
};
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string code;
countries.TryGetValue((string)value,out code);
if (code != null)
return new BitmapImage(new Uri("Flags\\" + code + ".png",
UriKind.Relative));
return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
Este código procura no dicionário o país e, caso encontre, cria um BitmapImage a partir da imagem que tem a sigla do país. Além do template para apresentação do país, criamos também um template para edição, que mostra uma combobox com os países.
Figura 7 – DataGrid com as bandeiras dos países
Se quisermos que uma coluna tenha uma largura diferente do padrão, podemos configurar a largura individual das colunas usando a propriedade Width. Se não quisermos que a coluna seja editável, podemos usar a propriedade IsReadOnly, configurando-a para True.
Mostrando os detalhes de uma linha
Além de mostrar e editar os dados em uma linha, podemos mostrar os detalhes da linha quando ela está selecionada. Para isso, devemos criar um DataTemplate e atribuí-lo à propriedade RowDetalisTemplate. Iremos colocar uma nova DataGrid que mostra os pedidos relativos àquele cliente. O LINQ to SQL traz os pedidos como uma propriedade da classe cliente e podemos atribuí-la à propriedade ItemsSource da nova DataGrid.
Para mostrar os pedidos, configuramos a propriedade RowDetailsTemplate como:
<dg:DataGrid.RowDetailsTemplate>
<DataTemplate>
<dg:DataGrid ItemsSource="{Binding Path=Orders}"
AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False"
AlternationCount="2"
ColumnHeaderStyle="{StaticResource
columnHeaderStyle}"
RowStyle="{StaticResource detailRowStyle}"
HeadersVisibility="Column"
ColumnWidth="100"
RowHeaderStyle="{StaticResource rowHeaderStyle}"
HorizontalGridLinesBrush="#DDDDDD"
VerticalGridLinesBrush="#DDDDDD">
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="Pedido"
Binding="{Binding Path=OrderID}"
IsReadOnly="True"/>
<dg:DataGridTemplateColumn Header="Data Pedido">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OrderDate,
StringFormat=d}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
<dg:DataGridTemplateColumn Header="Data Envio">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ShippedDate,
StringFormat=d}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
</DataTemplate>
</dg:DataGrid.RowDetailsTemplate>
Inicialmente, criamos nossa DataGrid e atribuímos os pedidos do cliente selecionado à propriedade ItemsSource . Configuramos as propriedades CanUserAddRows e CanUserDeleteRows para False, de modo que não seja possível adicionar nem deletar pedidos. Em seguida, definimos três colunas para a DataGrid: uma coluna de texto, com o número do pedido e duas colunas personalizadas, para mostrar a data sem a hora. Não criamos um CellEditingTemplate, pois as colunas não serão editáveis. Criamos também um novo estilo para as linhas, que atribuímos à propriedade RowStyle:
<Style x:Key="detailRowStyle" TargetType="dg:DataGridRow">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="10" />
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="#E5FFFF" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="#C6FFFC" />
</Trigger>
</Style.Triggers>
</Style>
Ao executarmos o programa, vemos que a DataGrid fica semelhante à Figura 8.
Figura 8 – DataGrid mostrando os pedidos do cliente selecionado
Agrupando dados na DataGrid
Outra caracaterística que pode ser acrescentada à DataGrid é o agrupamento de dados podemos agrupar os dados criando uma View para os dados, que os agrupa e, em seguida, criar um estilo para o grupo.
Por exemplo, para agruparmos os dados dos clientes por cidade, devemos criar uma nova View no construtor da janela:
public Window1()
{
InitializeComponent();
var dc = new NorthwindModelDataContext();
dataGrid.ItemsSource = dc.Customers.ToList();
ICollectionView view =
CollectionViewSource.GetDefaultView(dataGrid.ItemsSource);
view.SortDescriptions.Add(
new SortDescription("Country", ListSortDirection.Ascending));
view.SortDescriptions.Add(
new SortDescription("City", ListSortDirection.Ascending));
view.GroupDescriptions.Add(
new PropertyGroupDescription("City"));
}
Esta view classifica os dados por país e cidade, agrupando-os por cidade. Se rodarmos o programa agora, veremos que a única diferença é a classificação dos dados. Não há nenhuma indicação de agrupamento. Isto é devido ao fato que precisamos criar um GroupStyle para a DataGrid:
<dg:DataGrid.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource groupTemplate}" />
</dg:DataGrid.GroupStyle>
O template para o cabeçalho do grupo é:
<DataTemplate x:Key="GroupTemplate">
<Border Background="Navy" CornerRadius="10" Height="Auto"
Padding="10" Margin="5">
<TextBlock Text="{Binding Name}" Foreground="White"
FontFamily="Tahoma" FontSize="18" FontWeight="Bold"
VerticalAlignment="Center" Margin="5,0,0,0"/>
</Border>
</DataTemplate>
Colocamos uma borda com cantos arredondados e o nome da cidade. Com isso, nosso agrupamento está pronto e pode ser visto na Figura 9.
Figura 9 – Datagrid com agrupamentos
Conclusões
Como pudemos ver a nova DataGrid do WPF é bastante poderosa e apresenta inúmeras opções de personalização. Podemos customizar seu estilo, as colunas que são apresentadas e mesmo mostrar detalhes dos itens. Com isso, podemos apresentar nossos dados das mais diversas maneiras, sem perder os recursos que o WPF traz: data binding, estilos, templates, entre outros. Sem dúvida, é um acréscimo muito importante à paleta de ferramentas do WPF.