Neste artigo pretendo apresentar a Datagrid em Windows Presentation Foundation (WPF) na .Net Framework 4.0. Vou começar por uma breve apresentação teórica e em seguida irei apresentar vários exemplos. De salientar que não terei em conta Design Patterns.
A DataGrid é um controlo que permite apresentar dados, representando cada linha um item de uma lista de objectos do mesmo tipo e as colunas representam as várias características do objecto. Ou seja, se na instância da datagrid apresento uma lista de empregados, cada linha representa um empregado e cada coluna representa uma propriedade do empregado.
A classe DataGrid está incluída no namespace System.Windows.Controls e é um selector que permite seleccionar mais do que um item ao mesmo tempo e tem por base a classe ItemsControl, que é um Controlo e que implementa a interface IAddChild. A seguinte imagem mostra-nos esta hierarquia de classes.
Vejamos agora algumas propriedades, métodos e eventos relevantes da DataGrid.
Antes de passarmos aos exemplos práticos, vejamos os vários tipos de colunas da DataGrid.
Em WPF, podemos definir os seguintes tipos de colunas:
O seguinte diagrama, permite-nos perceber melhor qual a classe de base de cada tipo de coluna.
Passemos agora aos exemplos práticos.
Suponhamos que temos um objecto empregado (Employee) e um conjunto de empregados (Employees). O empregado, tem como principais características: código, nome, se é o patrão, salário, taxas e aniversário.
• A classe Employee implementa a interface INotifyPropertyChanged para que sejam feitas notificações quando alguma propriedade é alterada. Isto é em parte responsável pela actualização da informação na Datagrid.
• A classe Employees é uma ObservableCollection de Employee, porque pretendo que sejam feitas notificações quando um ou mais empregrados são adicionados/removidos da lista de empregados. Caso tivesse optado por List<Employee>, Collection<Employee> ou IEnumerable<Employee> essas notificações não aconteceriam. Na imagem anterior é possível analisar o que é uma ObservableCollection, sendo a implementação das interfaces INotifyCollectionChanged e INotifyPropertyChanged reponsável
Objectivo: Apresentar numa janela uma Datagrid com uma lista de empregados, as colunas da Datagrid devem ser geradas automaticamente:
<DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="True"/>
Usando a propriedade ItemsSource para atribuir a lista de empregados.
private void WindowLoaded(object sender, RoutedEventArgs e){ var employees=new Employees(); // add the employee list //using ItemsSource dataGridEmployees.ItemsSource = employees;
Usando a propriedade Items para atribuir a lista de empregados.
private void WindowLoaded(object sender, RoutedEventArgs e){ var employees=new Employees(); // add the employee list //using Items foreach (var employee in employees) dataGridEmployees.Items.Add(employee); }
Objectivo: Apresentar numa janela uma Datagrid com uma lista de empregados, as colunas da Datagrid não devem ser geradas automaticamente e apenas se pretende visualizar as colunas relativas ao nome, se é patrão, salário e taxas.
<DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Nome" MinWidth="200" Binding="{Binding Name}" /> <DataGridTextColumn Header="Salário" Width="100" MaxWidth="150" Binding="{Binding Salary}"/> <DataGridTextColumn Header="Taxa" Width="50" Binding="{BindingRates}"/> <DataGridCheckBoxColumn Header="É patrão" Binding="{BindingIsBoss}"/> </DataGrid.Columns> </DataGrid>
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); // add the employee list dataGridEmployees.ItemsSource = employees; }
(A imagem com o resultado pode ser visto na coluna do lado direito em cima de todo)
Objectivo: Formatar a apresentação do salário e das taxas, ou seja, um salário com valor 0 (zero) deve apresentar o valor “Não está atribuído” e os salários que estejam definidos devem apresentar o símbolo da moeda € (euro). As Taxas devem apresentar o valor da taxa com o símbolo de percentagem.
Neste exemplo, temos necessidade de criar duas classes, cada classe representa o conversor que será usado no objecto de Binding da coluna. O conversor será responsável por transformar o valor original no valor que é pretendido apresentar. Ambas as classes implementam a interface IValueConverter.
Portanto, o conversor para o salário será definido da seguinte forma:
[ValueConversion(typeof(double), typeof(string))] public class SalaryValueConverter:IValueConverter{ private const string NotDefinedValue = "Não está definido"; private const string EuroSymbol = "€"; public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value == null || (double.Parse(value.ToString()) == 0)) return NotDefinedValue; return string.Format("{0} {1}",value, EuroSymbol); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is string && value.ToString().Equals(NotDefinedValue)) return 0; return value is string && value.ToString().Contains(EuroSymbol)? double.Parse(value.ToString().Replace(EuroSymbol, string.Empty)) : value; } }
O conversor para a taxa será definido da seguinte forma:
[ValueConversion(typeof(int),typeof(string))] public class RatesValueConverter:IValueConverter{ private const string PercentageSymbol = "%"; public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture){ if (value is int && int.Parse(value.ToString()) == 0) return string.Empty; return string.Format("{0} {1}", value, PercentageSymbol); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture){ throw new NotImplementedException(); } }
É necessário adicionar o namespace dos resources
xmlns:Converters="clrnamespace:WPFDatagridImplementation.Converters"
Nos resources da janela, inicializamos os dois conversores
<Window.Resources> <Converters:RatesValueConverterx:Key="ratesValueConverter"/> <Converters:SalaryValueConverterx:Key="salaryValueConverter"/> </Window.Resources>
É necessário alterar o Binding das colunas, é neste objecto que definimos o conversor aplicar.
<DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Nome" MinWidth="200" Binding="{Binding Name}" /> <DataGridTextColumn Header="Salário" Width="100" MaxWidth="150" Binding="{BindingPath=Salary,Converter={StaticResourcesalaryValueConverter}}"/> <DataGridTextColumn Header="Taxa" Width="50" Binding="{BindingPath=Rates,Converter={StaticResourceratesValueConverter}}"/> <DataGridCheckBoxColumnHeader="É patrão" Binding="{BindingIsBoss}"/> </DataGrid.Columns> </DataGrid>
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); // add the employee list dataGridEmployees.ItemsSource = employees; }
Objectivo: Apresentar uma coluna extra, que será o resultado do produto do salário com a taxa associada.
É necessário criar um conversor, que receba o salário e a taxa, e devolva o resultado final, formado. Neste caso, como é preciso receber dois valores a classe terá que implementar a interface IMultiValueConverter.
public class FinalySalaryConverter:IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { double salary = 0.0; int rates = 0; if (values[0] is double) salary = double.Parse(values[0].ToString()); if (values[1] is int) rates = int.Parse(values[1].ToString()); if (salary == 0 && rates == 0) return string.Empty; return string.Format("{0} €", salary - salary * rates / 100); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
<Window.Resources> <Converters:RatesValueConverter x:Key="ratesValueConverter"/> <Converters:SalaryValueConverter x:Key="salaryValueConverter"/> <Converters:FinalySalaryConverter x:Key="finalySalaryConverter"/> </Window.Resources>
Adicionamos a nova coluna com o nome “Salário Final”, repare-se que usou-se um Multibinding, para permitir enviar o valor do salário e o valor da taxa.
<DataGrid.Columns> <DataGridTextColumn Header="Nome" MinWidth="200" Binding="{Binding Name}" /> <DataGridTextColumn Header="Salário" Width="100" MaxWidth="150" Binding="{BindingPath=Salary,Converter={StaticResourcesalaryValueConverter}}"/> <DataGridTextColumn Header="Taxa" Width="50" Binding="{BindingPath=Rates,Converter={StaticResourceratesValueConverter}}"/> <DataGridTextColumn Header="SalárioFinal"> <DataGridTextColumn.Binding> <MultiBindingConverter="{StaticResourcefinalySalaryConverter}"> <BindingPath="Salary"/> <Binding Path="Rates"/> </MultiBinding> </DataGridTextColumn.Binding> </DataGridTextColumn> <DataGridCheckBoxColumn Header="Épatrão" Binding="{Binding IsBoss}"/> </DataGrid.Columns> </DataGrid>
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); // add the employee list dataGridEmployees.ItemsSource = employees; }
Objectivo: Apresentar a coluna de Aniversário, recorrendo ao controlo DataPicker.
<DataGridTemplateColumn Header="Aniversário" MinWidth="100"> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding Birthday}" SelectedDateFormat="Short" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Birthday, StringFormat=d}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn>
Objectivo: Apresentar os empregados ordenados por Nome.
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); dataGridEmployees.ItemsSource = employees; ICollectionView view = CollectionViewSource.GetDefaultView(dataGridEmployees.ItemsSource); view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); }
Objectivo: Filtrar os empregados com salário superior a 500.00€.
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); dataGridEmployees.ItemsSource =employees; ICollectionView view = CollectionViewSource.GetDefaultView(dataGridEmployees.ItemsSource); view.Filter = new Predicate<object>(EmployeesWithSalaryGreaterThan500); } private bool EmployeesWithSalaryGreaterThan500(object param) { var employee = (Employee)param; if (employee.Salary > 500) return true; return false; }
Objectivo: As linhas da datagrid devem apresentar uma cor alternada.
<DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="True" AlternatingRowBackground="LightGray" AlternationCount="2"/>
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); dataGridEmployees.ItemsSource = employees; }
Objectivo: Alterar a altura do cabeçalho das colunas, alterar o tipo de fonte da letra e tamanho e cor. Alterar a cor das linhas horizontais e verticais da datagrid.
Nos resources da Window, definimos o estilo que iremos aplicar às células.
<Window.Resources> <Style x:Key="columnStyle" TargetType="DataGridColumnHeader"> <Setter Property="FontFamily" Value="Arial Black" /> <Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="Red"/> </Style> </Window.Resources> <DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="True" ColumnHeaderStyle="{DynamicResourcecolumnStyle}" ColumnHeaderHeight="40" GridLinesVisibility="All" HorizontalGridLinesBrush="Red" VerticalGridLinesBrush="Green"/>
private void WindowLoaded(object sender, RoutedEventArgs e){ var employees = new Employees(); dataGridEmployees.ItemsSource = employees; }
Objectivo: Ao seleccionar uma célula e mostrar uma mensagem de aviso de que foi detectada uma alteração, apresentando o nome do empregado seleccionado e o nome da coluna seleccionada.
<DataGrid Name="dataGridEmployees" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" AutoGenerateColumns="True" SelectionMode="Single" SelectionUnit="Cell" CurrentCellChanged="DataGridEmployeesCurrentCellChanged"/>
private void WindowLoaded(object sender, RoutedEventArgs e) { var employees = new Employees(); dataGridEmployees.ItemsSource = employees; } private void DataGridEmployeesCurrentCellChanged(objectsender, System.EventArgs e) { Employee selectedEmployee = (Employee)dataGridEmployees.CurrentItem; string message = String.Format("Foi seleccionada a coluna '{0}' do empregado(a) {1}.", dataGridEmployees.CurrentCell.Column.Header,selectedEmployee.Name); MessageBox.Show(message); }
Demo do artigo: http://bit.ly/efSP1W
Contém:
Link para o artigo: http://tinyurl.com/RPED28-05
É licenciada em Matemática – Especialidade em Computação, pela Universidade de Coimbra, actualmente é Software Developer no Porto. O entusiasmo pela área resultou na obtenção dos títulos de Microsoft Certified Profissional Developer – Windows 3.5, Microsoft Certified Tecnology Specialist – WPF 3.5, WPF 4 e Windows Forms. Faz parte de várias comunidades, tendo uma participação activa na Comunidade NetPonto e no P@P.