WPF - Apresentando e usando Data Views


Neste artigo eu vou tratar das Views em aplicações WPF, e , se você esta começando com WPF agora, sugiro que você leia os meus artigos apresentando os conceitos básicos da WPF antes de continuar a sua leitura.

Em uma aplicação WPF quando você vincula uma coleção (ou um DataTable) a um ItemsControl, um data view ou visão de dados é criado implicitamente por "trás dos panos". Essa visão se situa entre a fonte de dados e controle vinculado. A exibição de dados é uma janela em sua fonte de dados. Ele rastreia o item atual, e suporta características como a classificação, filtragem e agrupamento.

Estas características são independentes dos dados do objeto em si, o que significa que você pode vincular os mesmos dados de diferentes maneiras em diferentes partes de uma janela (ou em diferentes partes do seu aplicativo). Por exemplo, você pode vincular a mesma coleção de produtos para duas listas diferentes, mas filtrá-los para mostrar registros diferentes.

O objeto view que é usado depende do tipo de objeto de dados. Todas as views derivam de CollectionView, mas duas implementações especializadas derivam de CollectionView: ListCollectionView e BindingListCollectionView. A seguir temos um resumo de como elas funcionam:

Obs: É bom não usar o terceiro cenário. Uma CollectionView oferece um desempenho ruim para muitos itens e operações que modificam a fonte de dados (tais como inserções e deleções)

Para obter um objeto de exibição que está atualmente em uso, você usa o GetDefaultView compartilhado da classe System.Windows.Data.CollectionViewSource.

Quando você chama GetDefaultView (), você passa a fonte de dados a coleção ou DataTable que você está usando. Aqui está um exemplo que é uma visão de coleta de produtos que está vinculado à uma lista:

Dim view As ICollectionView
view = CollectionViewSource.GetDefaultView(lstProdutos.ItemsSource)

O método GetDefaultView () sempre retorna uma referência ICollectionView. Cabe a você converter o objeto view para a respectiva classe, tal como um ListCollectionView ou BindingListCollectionView, dependendo da fonte de dados. Exemplo:

Dim view As ListCollectionView = CType(CollectionViewSource.GetDefaultView(lstProdutos.ItemsSource), ListCollectionView)

Dessa forma o ICollectionView é o objeto de dados primário para qualquer controles de lista da WPF (como ComboBox, ListBox, ListView, etc.) que permite flexibilidade como classificação, filtragem, agrupamento, e navegação, garantindo que todas as informações relacionadas, como filtragem, classificação, etc, estejam dissociadas do controle real.

Já a CollectionView é uma camada que funciona sobre os objetos de dados que permite que você defina regras para classificação, filtragem, agrupamento, etc, e manipule a exibição de dados em vez de modificar os objetos de dados reais. Portanto, em outras palavras, uma CollectionView é uma classe que cuida totalmente da visão dando-nos a capacidade para lidar com determinadas características incluídas dentro dela.

Navegando com uma View

Uma das coisas mais simples que você pode fazer com um objeto View é determinar o número de itens na lista(através da propriedade Count) e obter uma referência ao objeto de dados atual (CurrentItem) ou índice de posição atual (CurrentPosition).

Você também pode usar um punhado de métodos para mover de um registro para outro, como MoveCurrentToFirst (), MoveCurrentToLast (), MoveCurrentToNext (),MoveCurrentToPrevious(), and MoveCurrentToPosition().

Se você quer criar um aplicativo de navegação de registro, você pode querer fornecer seus próprios botões de navegação e usar estes recursos para implementar a navegação.

A vinculação de TextBox que exibem dados permanece da mesma forma, somente precisando indicar a propriedade adequada conforme o código a seguir: Exemplo de vinculação de uma propriedade Nome em um TextBlock:

<TextBlock Margin="7">Nome :</TextBlock>
<TextBox Margin="5" Grid.Column="1" Text="{Binding Path=Nome}"></TextBox>

Da teoria para a Prática

Vamos agora mostrar um exemplo básico envolvendo os conceitos apresentados mostrando como podemos filtrar, agrupar, ordenar e navegar em uma fonte de dados usando views.

Como já mencionei para boter uma CollectionView a partir de um Enumerable é realmente muito simples , basta passar o Enumerable para CollectionViewSource.GetDefaultView. Se estivermos usando um controle ListBox podemos fazer assim:

VB .NET
Me.ListboxControl.ItemsSource = CollectionViewSource.GetDefaultView(Me.Source)		

C#

this.ListboxControl.ItemsSource = CollectionViewSource.GetDefaultView(this.Source);
Dessa forma a lista irá obter a CollectionView requerida.

Assim, o CollectionView realmente separa o objeto View do controle de lista com o DataSource real e, portanto, fornece uma interface para manipular os dados antes de refletir com os objetos View. Agora vamos ver como implementar os recursos básicos para a ICollectionView.

A figura abaixo mostra esta separação :

Vejamos agora como realizar as principais operações usando a View.

Classificação

A Classificação pode ser aplicada ao CollectionView de uma maneira muito fácil. Você precisa adicionar um SortDescription ao CollectionView. O CollectionView realmente mantém uma pilha de objetos SortDescription, sendo que cada um deles sendo uma estrutura que pode conter informações de uma coluna e uma direção de ordenação. Você pode adicioná-los no CollectionView para obter uma saída desejada.

Assim seu eu guardar a CollectionView como uma propriedade:   

VB .NET

Private Property Source() As ICollectionView
Get
     Return m_Source
End Get
Set
     m_Source = Value
End Set
End Property
Private m_Source As ICollectionView

C#

ICollectionView Source { get; set; }

Para classificar a coleção existente basta fazer assim:

VB .NET :  Me.Source.SortDescriptions.Add(New SortDescription("Nome", ListSortDirection.Descending))
C#         :  this.Source.SortDescriptions.Add(new SortDescription("Nome", ListSortDirection.Descending));

Aqui a CollectionView será classificada com base no nome e em ordem decrescente.

Obs: O comportamento padrão do CollectionView e atualizado automaticamente quando um SortDescription novo é adicionado a ele. Por questão de desempenho, você pode usar DeferRefresh() se você quiser atualizar apenas uma vez para adicionar mais de uma vez SortDescription

Agrupamento

Você pode criar grupos personalizados para ICollectionView da mesma forma como você faz para a classificação. A seguir temos um exemplo para criar um grupo de elementos que você precisa para usar GroupStyle para definir o modelo para o Grupo e para mostrar o nome do grupo no cabeçalho de grupo.

<ListView.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Nome}" />
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListView.GroupStyle>

Aqui definimos o Grupo HeaderTemplate para cada grupo de modo que o TextBlock mostra o nome do item do Grupo, através da qual o agrupamento é feito. Você pode especificar mais de um agrupamento de informações para uma única coleção. Para agrupar essa coleção por Setor você precisa usar o seguinte código:

VB .NET
Me.Source.GroupDescriptions.Add(New PropertyGroupDescription("Setor"))
C# this.Source.GroupDescriptions.Add(new PropertyGroupDescription("Setor"));

Obs: Realizar um agrupamento desliga virtualização. Então, se você está lidando com uma grande quantidade de dados, realizar agrupamentos pode levar a problema de desempenho.

Filtragem

A Filtragem requer um delegado (predicado) com base no qual o filtro irá ocorrer. O predicado leva em conta um item e com base no valor de verdadeiro ou falso que ele retorna , seleciona ou não um elemento.

VB .NET C#
Me.Source.Filter = Function(item)
Dim vitem As ViewItem = TryCast(item, ViewItem)
If vitem Is Nothing Then
    Return False
End If
  Return vitem.Name.Contains("A")
End Function
this.Source.Filter = item =>
{
  ViewItem vitem = item as ViewItem;
  if (vitem == null) return false;
   return vitem.Name.Contains("A");
};

Este código irá selecionar somente os elementos que possuem o caractere A em seus nomes.

Manipulação de registros

A ICollectionView também permite sincronizar itens com a posição atual do elemento na CollectionView. Cada ItemsControl que é a classe base de qualquer ListControl em WPF expõe uma propriedade chamada IsSynchronizedWithCurrentItem que quando definida como true automaticamente mantém a posição atual do CollectionView em sincronia.

Com base nisso , para manipular os registros podemos usar os seguintes métodos:

VB .NET

C#

Me.Source.MoveCurrentToFirst()
Me.Source.MoveCurrentToPrevious()
Me.Source.MoveCurrentToNext()
Me.Source.MoveCurrentToLast()

this.Source.MoveCurrentToFirst();
this.Source.MoveCurrentToPrevious();
this.Source.MoveCurrentToNext();
this.Source.MoveCurrentToLast();

Estes métodos permitem navegar em torno do CurrentItem do CollectionView.Você também pode usar o evento CurrentChanged para interceptar sua seleção lógica ao redor do objeto.

Juntando todas as partes

Vou apresentar agora uma pequena aplicação WPF de exemplo que mostra como usar na prática todos os conceitos abordados filtrando e agrupando informações de funcionários de uma empresa.

Abra o Visual C# 2010 Express Edition e crie uma nova aplicação WPF Application com o nome CollectionviewExemplo;

A seguir no arquivo MainWindow.xaml vamos definir a interface com o usuário usando o leiaute definido via código XAML conforme a figura abaixo:

O código XAML correspondente é visto a seguir:

<Window x:Class="CollectionviewSourceSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CollectionviewSourceSample" 
        x:Name="mainWin"
        Title="WPF - Filtrando e Agrupando" Height="344" Width="626">
    <Window.Resources>
        <DataTemplate x:Key="HeaderTemplateArrowUp">
            <DockPanel>
                <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
                <Path  x:Name="arrow"
                       StrokeThickness = "1"
                       Fill = "Gray"
                       Data = "M 5,10 L 15,10 L 10,5 L 5,10"/>
            </DockPanel>
        </DataTemplate>
        <DataTemplate x:Key="HeaderTemplateArrowDown">
            <DockPanel>
                <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
                <Path   x:Name="arrow"
                        StrokeThickness = "1"
                        Fill = "Gray"
                        Data = "M 5,5 L 10,10 L 15,5 L 5,5"/>
            </DockPanel>
        </DataTemplate>
        
        <local:BooleanVisiblityConverter x:Key="convVis"/>
        <Style x:Key="ListViewUnSelected" TargetType="{x:Type TextBlock}">
            <Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type ListViewItem}}, Converter={StaticResource convVis}, ConverterParameter=False}" />
        </Style>
        <Style x:Key="ListViewSelected" TargetType="{x:Type FrameworkElement}">
            <Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type ListViewItem}}, Converter={StaticResource convVis}, ConverterParameter=True}" />
        </Style>
    </Window.Resources>
    <Grid x:Name="grdMain">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
        <GroupBox Header="Filtro">
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="Filtrar Por :" />
                <ComboBox ItemsSource="{Binding Columns}" x:Name="cmbProperty" />
                <TextBox x:Name="txtFilter" MinWidth="50" />
                <Button x:Name="btnFilter" Click="btnFilter_Click" Content="Aplicar Filtro"/>
                <Button x:Name="btnClear" Click="btnClear_Click" Content="Limpar Filtro"/>
            </StackPanel>
        </GroupBox>
            <GroupBox Header="Agrupar" Height="50" Width="223">
                <StackPanel Orientation="Horizontal" Height="29" Width="205">
                    <TextBlock Text="Agrupar Por :" />
                    <ComboBox ItemsSource="{Binding Columns}" x:Name="cmbGroups" />
                    <Button x:Name="btnGroup" Click="btnGroup_Click"  Content="Agrupar"/>
                    <Button x:Name="btnClearGr" Click="btnClearGr_Click"  Content="Limpar"/>
                </StackPanel>
            </GroupBox>
        </StackPanel>
        <ListView ItemsSource="{Binding}" x:Name="lvItems" GridViewColumnHeader.Click="ListView_Click" 
IsSynchronizedWithCurrentItem="True" Grid.Row="1">
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Nome}" />
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
            <ListView.View>
                <GridView AllowsColumnReorder="True">
                    <GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}" />
                    <GridViewColumn Header="Nome">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Grid>
                                    <TextBlock Text="{Binding Path=Nome}" Style="{StaticResource ListViewUnSelected}"/>
                                    <TextBox Text="{Binding Path=Nome}" Style="{StaticResource ListViewSelected}" />
                                </Grid>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Programador">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Grid>
                                    <TextBlock Text="{Binding Path=Programador}" Style="{StaticResource ListViewUnSelected}" />
                                    <ComboBox SelectedItem="{Binding Path=Programador}" ItemsSource="{Binding ElementName=mainWin, 
Path=DeveloperList}" Style="{StaticResource ListViewSelected}" />
                                </Grid>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Salario">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Grid>
                                    <TextBlock Text="{Binding Path=Salario}" Style="{StaticResource ListViewUnSelected}" />
                                    <TextBox Text="{Binding Path=Salario}" Style="{StaticResource ListViewSelected}" />
                                </Grid>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
            <ListView.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF00B0FE" Offset="0" />
                    <GradientStop Color="Cyan" Offset="0.994" />
                </LinearGradientBrush>
            </ListView.Background>
        </ListView>
        <StackPanel Orientation="Horizontal" Grid.Row="2">
            <Button Content="&lt;|" Click="btnNavigation_Click" Tag="0" />
            <Button Content="&lt;" Click="btnNavigation_Click" Tag="1" />
            <Button Content="&gt;" Click="btnNavigation_Click" Tag="2" />
            <Button Content="|&gt;" Click="btnNavigation_Click" Tag="3" />
            <Button HorizontalAlignment="Right" x:Name="btnEvaluate" Content="Exibir Objeto Selecionado" Click="btnEvaluate_Click" />
        </StackPanel>
    </Grid>
</Window>

Definindo o modelo da aplicação

No menu Project selecione Add Class e informe o nome Model.cs. Neste arquivo vamos definir o modelo da nossa aplicação

using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;

namespace CollectionviewSourceSample
{
    public class Model
    {

        private ObservableCollection<ViewItem> items;
        public ObservableCollection<ViewItem> Items
        {
            get
            {
                this.items = this.items ?? this.LoadItems();
                return this.items;
            }
        }

        public IEnumerable<string> Columns
        {
            get
            {
                return from prop in typeof(ViewItem).GetProperties()
                       select prop.Name;
            }
        }
        public IEnumerable<string> AvailableDevelopment
        {
            get
            {
                return (from developer in this.Items
                        select developer.Programador).Distinct();
            }
        }
        private ObservableCollection<ViewItem> LoadItems()
        {
            ObservableCollection<ViewItem> items = new ObservableCollection<ViewItem>();

            items.Add(new ViewItem { Id = "1", Nome = "Macoratti", Programador = "Visual Basic", Salario = 59000.00f });
            items.Add(new ViewItem { Id = "2", Nome = "Jefferson", Programador = "ASP.NET", Salario = 89000.00f });
            items.Add(new ViewItem { Id = "3", Nome = "Janice", Programador = "Visual C#T", Salario = 95000.00f });
            items.Add(new ViewItem { Id = "4", Nome = "Giovani", Programador = "Silverlight", Salario = 26000.00f });
            items.Add(new ViewItem { Id = "5", Nome = "Mario", Programador = "ASP.NET", Salario = 78000.00f });
            items.Add(new ViewItem { Id = "6", Nome = "Marcia", Programador = "WPF", Salario = 37000.20f });
            items.Add(new ViewItem { Id = "7", Nome = "Jessica", Programador = "Silverlight", Salario = 45000.00f });
            items.Add(new ViewItem { Id = "8", Nome = "Jose Carlos", Programador = "SQL Server", Salario = 70000.00f });
            items.Add(new ViewItem { Id = "9", Nome = "Ramirez", Programador = "WPF", Salario = 40000.00f });

            return items;
        }
    }
    public class ViewItem
    {
        public string Id { get; set; }
        public string Nome { get; set; }
        public string Programador { get; set; }
        public float Salario { get; set; }
    }
}

A seguir no arquivo MainWindow.xaml.cs inclua o seguinte código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Reflection;

namespace CollectionviewSourceSample
{
    public partial class MainWindow : Window
    {
        private Model DataModel
        {
            get;
            set;
        }
        public MainWindow()
        {
            InitializeComponent();

            this.DataModel = new Model();

            this.Source = CollectionViewSource.GetDefaultView(this.DataModel.Items);
            this.grdMain.DataContext = this.DataModel;
            this.lvItems.DataContext = this.Source;
        }

        ICollectionView Source { get; set; }

        public IEnumerable<string> DeveloperList
        {
            get
            {
                return this.DataModel.AvailableDevelopment;
            }
        }

        private void ListView_Click(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader currentHeader = e.OriginalSource as GridViewColumnHeader;
            if(currentHeader != null && currentHeader.Role != GridViewColumnHeaderRole.Padding)
            {
                using (this.Source.DeferRefresh())
                {
                    Func<SortDescription, bool> lamda = item => item.PropertyName.Equals(currentHeader.Column.Header.ToString());
                    if (this.Source.SortDescriptions.Count(lamda) > 0)
                    {
                        SortDescription currentSortDescription = this.Source.SortDescriptions.First(lamda);
                        ListSortDirection sortDescription = currentSortDescription.Direction == ListSortDirection.Ascending ? 
ListSortDirection.Descending : ListSortDirection.Ascending;


                        currentHeader.Column.HeaderTemplate = currentSortDescription.Direction == ListSortDirection.Ascending ?
                            this.Resources["HeaderTemplateArrowDown"] as DataTemplate : this.Resources["HeaderTemplateArrowUp"] as DataTemplate;

                        this.Source.SortDescriptions.Remove(currentSortDescription);
                        this.Source.SortDescriptions.Insert(0, new SortDescription(currentHeader.Column.Header.ToString(), sortDescription));
                    }
                    else
                        this.Source.SortDescriptions.Add(new SortDescription(currentHeader.Column.Header.ToString(), ListSortDirection.Ascending));
                }                 
            }
        }

        private void btnFilter_Click(object sender, RoutedEventArgs e)
        {
            this.Source.Filter = item =>
            {
                ViewItem vitem = item as ViewItem;
                if (vitem == null) return false;

                PropertyInfo info = item.GetType().GetProperty(cmbProperty.Text);
                if (info == null) return false;

                return  info.GetValue(vitem,null).ToString().Contains(txtFilter.Text);
            };
        }

        private void btnClear_Click(object sender, RoutedEventArgs e)
        {
            this.Source.Filter = item => true;
        }

        private void btnGroup_Click(object sender, RoutedEventArgs e)
        {
            this.Source.GroupDescriptions.Clear();

            PropertyInfo pinfo = typeof(ViewItem).GetProperty(cmbGroups.Text);
            if (pinfo != null)
                this.Source.GroupDescriptions.Add(new PropertyGroupDescription(pinfo.Name));

        }

        private void btnClearGr_Click(object sender, RoutedEventArgs e)
        {
            this.Source.GroupDescriptions.Clear();
        }

        private void btnNavigation_Click(object sender, RoutedEventArgs e)
        {
            Button CurrentButton = sender as Button;

            switch (CurrentButton.Tag.ToString())
            {
                case "0":
                    this.Source.MoveCurrentToFirst();
                    break;
                case "1":
                    this.Source.MoveCurrentToPrevious();
                    break;
                case "2":
                    this.Source.MoveCurrentToNext();
                    break;
                case "3":
                    this.Source.MoveCurrentToLast();
                    break;
            }
        }

        private void btnEvaluate_Click(object sender, RoutedEventArgs e)
        {
            ViewItem item = this.lvItems.SelectedItem as ViewItem;

            string msg = string.Format("{0}, Programador {1} Salário de {2}", item.Nome, item.Programador, item.Salario);
            MessageBox.Show(msg);
        }
    }
}

Executando o projeto iremos obter:

Esta pequena aplicação permite que você confira os recursos explanados no arquivo como filtrar, agrupar , navegar e acessar uma View na sua aplicação WPF.

Pegue o projeto completo aqui:    CollectionviewExemplo.zip

Aguarde mais artigos sobre WPF. 

"Em verdade vos digo que, qualquer que não receber o reino de Deus como menino, não entrará nele." (Lucas 18:17)

Referências:


José Carlos Macoratti