Essa semana no fórum de WPF do MSDN o Márcio Fábio Althmann postou uma pergunta interessante em uma thread e resolvi compartilhar aqui a minha experiência tentando resolver o problema. Na verdade eu consegui encontrar uma maneira de resolvê-lo, mas quando fui postar a resposta, vi que o Roberto Sonnino postou uma solução melhor que a minha. Vou demonstrar neste post duas maneiras de resolver o problema.
A dúvida do Márcio era como alterar o background das células (DataGridCell) de um DataGrid com base no valor da célula e sem conhecer o nome da coluna associada à célula, ou seja, desconhecendo o Binding Path. A única especificação seria que quando a célula tivesse o valor “Livre” o fundo deveria ser verde e quando o valor fosse “Reservado” o fundo deveria ser vermelho.
A thread pode ser consultada no link abaixo.
Pintar célula
http://bit.ly/cjvkyv
Vou mostrar primeiro a solução quando conhecemos o nome da propriedade, ou seja, o Binding Path.
O que vamos fazer primeiramente é criar uma classe que implementa a interface IValueConverter e realiza a conversão de uma String em um SolidColorBrush por meio de uma lógica qualquer, no caso a cor será determinada pelos valores “Livre” ou “Reservado”. Um Converter (classe que implementa IValueConverter) é uma classe que realiza uma lógica customizada de binding.
A classe então fica da seguinte maneira:
[ValueConversion(typeof(string), typeof(SolidColorBrush))] public class DataGridCellBackgroundColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string valor = value.ToString(); if (valor == "Livre") return new SolidColorBrush(Colors.Green); else if (valor == "Reservado") return new SolidColorBrush(Colors.Red); return new SolidColorBrush(Colors.White); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
No arquivo XAML teríamos então que incluir o Converter como um Resource da Window.
<Window.Resources> <local:DataGridCellBackgroundColorConverter x:Key="DataGridCellConverter" /> </Window.Resources>
No DataGrid poderíamos então incluir um estilo com um setter para a propriedade Background usando o Binding para uma propriedade chamada NomePropriedade e também convertendo essa propriedade usando o Converter. Um tanto complicado né? Não é não, WPF é assim mesmo e com o tempo a gente acostuma.
<DataGrid AutoGenerateColumns="True" Name="seuDataGrid" ItemsSource="{Binding}"> <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Background" Value="{Binding NomePropriedade, Converter={StaticResource DataGridCellConverter}}" /> </Style> </DataGrid.CellStyle> </DataGrid>
Tudo ok e tudo funcionando, mas e quando não temos o nome da coluna? Ou seja, no XAML acima imagine que não sabemos qual é o NomePropriedade a ser utilizado. Para isso, uma das soluções é realizar o binding por meio do código, usando o data source para incluir as colunas (DataGridTextColumn no caso). Essa foi a solução que encontrei para o problema do Márcio.
Assim, o DataGrid agora fica da seguinte maneira no XAML.
<DataGrid AutoGenerateColumns="False" Name="seuDataGrid" ItemsSource="{Binding}" />
No evento Loaded da Window vamos popular um DataSet que funcionará como o data source e então vamos adicionar colunas do tipo DataGridTextColumn com os respectivos estilos e bindings. Os comentários do código servem como explicação do que está sendo realizado.
void SuaWindow_Loaded(object sender, RoutedEventArgs e) { // criação do DataSet var dataSet = new DataSet(); dataSet.Tables.Add("Tabela1"); dataSet.Tables[0].Columns.Add("Coluna1"); dataSet.Tables[0].Columns.Add("Coluna2"); dataSet.Tables[0].Rows.Add(new object[] { "Reservado", "Livre" }); dataSet.Tables[0].Rows.Add(new object[] { "Teste", "Livre" }); dataSet.Tables[0].Rows.Add(new object[] { "Livre", "Livre" }); dataSet.Tables[0].Rows.Add(new object[] { "Livre", "Reservado" }); dataSet.Tables[0].Rows.Add(new object[] { "Reservado", "Reservado" }); // conversor para as células var backColorConverter = new DataGridCellBackgroundColorConverter(); foreach (DataColumn dc in dataSet.Tables[0].Columns) { // cria coluna var dataGridTextColumn = new DataGridTextColumn(); // header e data binding dataGridTextColumn.Header = dc.ColumnName; dataGridTextColumn.Binding = new Binding(dc.ColumnName); // estilo da coluna var columnStyle = new Style(); columnStyle.TargetType = typeof(DataGridCell); // setter para a propriedade Background var setter = new Setter(); setter.Property = DataGridCell.BackgroundProperty; // binding do setter com o nome da coluna e converter var setterBinding = new Binding(dc.ColumnName); setterBinding.Converter = backColorConverter; setter.Value = setterBinding; // adiciona setter na coleção de setters do estilo columnStyle.Setters.Add(setter); // define o estilo da coluna dataGridTextColumn.CellStyle = columnStyle; // adiciona coluna no DataGrid this.seuDataGrid.Columns.Add(dataGridTextColumn); } this.seuDataGrid.DataContext = dataSet.Tables[0]; }
Acho uma solução simples, mas que ainda deve ser revista, visto que o tipo da coluna do DataGrid depende do tipo da coluna do data source.
Vamos agora então à solução do Roberto que sugere a criação de um Converter para pegar o valor da célula usando Reflection.
public class CelulaParaValorConverter: IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // célula var cell = value as DataGridCell; if (cell != null) { // linha e coluna var row = DataGridRow.GetRowContainingElement(cell); var column = cell.Column as DataGridBoundColumn; if (column != null) { // binding associado à coluna var binding = column.Binding as Binding; if (binding != null) { // nome da propriedade (Binding Path) string boundPropertyName = binding.Path.Path; // data item que a row representa object data = row.Item; // propriedades do item var properties = TypeDescriptor.GetProperties(data); // verifica total de propriedades if (properties.Count > 0) { // propriedade associada ao binding var property = properties[boundPropertyName]; // valor da propriedade associado ao item return property.GetValue(data); } } } } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
Adicionamos o Converter aos Resources da Window.
<Window.Resources> <local:CelulaParaValorConverter x:Key="CelulaParaValor" /> </Window.Resources>
Agora usamos um DataTrigger no estilo do DataGridCell para definir um Setter e também a propriedade RelativeSource para realizar binding com o próprio elemento (RelativeSource Self). O XAML fica da seguinte maneira:
<DataGrid AutoGenerateColumns="False" Name="seuDataGrid" ItemsSource="{Binding}"> <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Style.Triggers> <DataTrigger Value="Livre" Binding="{Binding RelativeSource, RelativeSource={RelativeSource Self}, Converter={StaticResource CelulaParaValor}}"> <Setter Property="Background" Value="Green" /> </DataTrigger> <DataTrigger Value="Reservado" Binding="{Binding RelativeSource, RelativeSource={RelativeSource Self}, Converter={StaticResource CelulaParaValor}}"> <Setter Property="Background" Value="Red" /> </DataTrigger> </Style.Triggers> </Style> </DataGrid.CellStyle> </DataGrid>
Agora é só realizar o binding do DataGrid com o data source.
this.seuDataGrid.DataContext = MetodoQueRetornaUmDataSource();
A solução do Roberto é realmente mais simples e elegante.
Até a próxima !
Binding Class
http://bit.ly/dr7GrM
DataTrigger Class
http://bit.ly/bL6rKS
Nenhum comentário:
Postar um comentário