Visão geral da associação de dados (WPF .NET)

A associação de dados no WPF (Windows Presentation Foundation) fornece uma maneira simples e consistente para os aplicativos apresentarem e interagirem com os dados. Os elementos podem ser associados a dados de diferentes tipos de fontes de dados na forma de objetos .NET e XML. Qualquer ContentControl como Button e qualquer ItemsControl, como ListBox e ListView, têm funcionalidade interna para habilitar o estilo flexível de itens de dados únicos ou coleções de itens de dados. É possível gerar exibições com classificação, filtragem e agrupamento dos dados.

A associação de dados no WPF tem várias vantagens em relação aos modelos tradicionais, incluindo suporte inerente à associação de dados por uma ampla gama de propriedades, representação flexível de dados da interface do usuário e separação limpa da lógica de negócios da interface do usuário.

Este artigo discute primeiramente os conceitos fundamentais para a associação de dados do WPF e aborda o uso da classe Binding e outros recursos da associação de dados.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

O que é a associação de dados?

A associação de dados é o processo que estabelece uma conexão entre a interface do usuário do aplicativo e os dados exibidos. Se a associação possui configurações corretas e os dados fornecem notificações adequadas, quando os dados mudam de valor, os elementos que são associados a dados refletem as mudanças automaticamente. A vinculação de dados também poderá significar que, se houver uma alteração de uma representação externa dos dados em um elemento, os dados subjacentes poderão ser atualizados automaticamente para refletir essa alteração. Por exemplo, se o usuário editar o valor em um elemento TextBox, o valor de dados subjacente será atualizado automaticamente para refletir essa alteração.

Um uso típico da associação de dados é colocar dados de configuração do servidor ou local em formulários ou outros controles de interface do usuário. No WPF, esse conceito é expandido para incluir a associação de uma ampla gama de propriedades a diferentes tipos de fontes de dados. No WPF, as propriedades de dependência de elementos podem ser associadas a objetos .NET (incluindo objetos ADO.NET ou objetos associados a serviços Web e propriedades da Web) e dados XML.

Conceitos básicos de associação de dados

Independentemente do elemento que você está associando e da natureza da fonte de dados, cada associação sempre segue o modelo ilustrado pela figura a seguir.

Diagram that shows the basic data binding model.

Como a figura mostra, a associação de dados é essencialmente a ponte entre seu destino de associação e sua fonte de associação. A figura demonstra os seguintes conceitos fundamentais de associação de dados do WPF:

  • Normalmente, cada associação tem quatro componentes:

    • Um objeto de destino de associação.
    • Uma propriedade de destino.
    • Uma origem de associação.
    • Um caminho para o valor na fonte de associação a ser usada.

    Por exemplo, se você associar o conteúdo de um TextBox à propriedade Employee.Name, configurará sua associação como a seguinte tabela:

    Configuração Valor
    Destino TextBox
    Propriedade de destino Text
    Objeto de origem Employee
    Caminho do valor do objeto de origem Name
  • A propriedade de destino deve ser uma propriedade de dependência.

    A maioria das propriedades UIElement são propriedades de dependência e a maioria das propriedades de dependência, exceto as somente leitura, dão suporte à associação de dados por padrão. Somente tipos derivados de DependencyObject podem definir propriedades de dependência. Todos os tipos de UIElement derivam de DependencyObject.

  • As fontes de associação não são restritas a objetos .NET personalizados.

    Embora não seja mostrado na figura, deve-se observar que o objeto de origem de associação não está restrito a ser um objeto .NET personalizado. A associação de dados do WPF dá suporte a dados na forma de objetos .NET, XML e até objetos de elemento XAML. Para fornecer alguns exemplos, sua fonte de associação pode ser um UIElement, qualquer objeto de lista, um objeto ADO.NET ou serviços Web ou um XmlNode que contenha seus dados XML. Para obter mais informações, confira a Visão geral das fontes de associação.

É importante lembrar que, quando você está estabelecendo uma associação, está associando um destino de associação a uma origem de associação. Por exemplo, se você estiver exibindo alguns dados XML subjacentes em um ListBox usando a associação de dados, estará associando seu ListBox aos dados XML.

Para estabelecer uma associação, use o objeto Binding. O restante deste artigo discute muitos dos conceitos associados e algumas das propriedades e o uso do objeto Binding.

Contexto de dados

Quando a associação de dados é declarada em elementos XAML, eles resolvem a associação de dados examinando sua propriedade DataContext imediata. O contexto de dados normalmente é o objeto de origem de associação para a avaliação do caminho do valor de origem de associação. Você pode substituir esse comportamento na associação e definir um valor de objeto de origem de associação específico. Se a propriedade DataContext do objeto que hospeda a associação não estiver definida, a propriedade DataContext do elemento pai será verificada e assim por diante, até a raiz da árvore de objetos XAML. Em suma, o contexto de dados usado para resolver a associação é herdado do pai, a menos que explicitamente definido no objeto.

As associações podem ser configuradas para serem resolvidas com um objeto específico, em vez de usar o contexto de dados para resolução de associação. Especificar um objeto de origem diretamente é usado quando, por exemplo, você associa a cor de primeiro plano de um objeto à cor da tela de fundo de outro objeto. O contexto de dados não é necessário, pois a associação é resolvida entre esses dois objetos. Inversamente, as associações que não estão associadas a objetos de origem específicos usam a resolução de contexto de dados.

Quando a propriedade DataContext é alterada, todas as associações que podem ser afetadas pelo contexto de dados são reavaliadas.

Direção do fluxo de dados

Conforme indicado pela seta na figura anterior, o fluxo de dados de uma associação pode ir do destino de associação para a origem da associação (por exemplo, o valor de origem muda quando um usuário edita o valor de um TextBox) e/ou da fonte de associação para o destino de associação (por exemplo, seu conteúdo de TextBox é atualizado com alterações na fonte de associação) se a fonte de associação fornecer as notificações adequadas.

Talvez você queira que seu aplicativo permita que os usuários alterem os dados e os propaguem de volta para o objeto de origem. Ou talvez você não queira permitir que os usuários atualizem os dados de origem. Você pode controlar o fluxo de dados definindo o Binding.Mode.

Esta figura ilustra os diferentes tipos de fluxo de dados:

Data binding data flow

  • OneWay associação faz com que as alterações na propriedade de origem atualizem automaticamente a propriedade de destino, mas as alterações na propriedade de destino não são propagadas de volta para a propriedade de origem. Esse tipo de associação será apropriado se o controle associado for somente leitura de forma implícita. Por exemplo, você pode associar a uma origem, como uma cotação da bolsa, ou talvez sua propriedade de destino não tenha nenhuma interface de controle fornecida para fazer alterações, como uma cor de plano de fundo associada a dados de uma tabela. Se não houver necessidade de monitorar as alterações da propriedade de destino, usar o OneWaymodo de associação evitará a sobrecarga do TwoWay modo de associação.

  • TwoWay associação faz com que as alterações na propriedade de origem ou na propriedade de destino atualizem automaticamente a outra. Esse tipo de associação é apropriado para formulários editáveis ou outros cenários de interface do usuário totalmente interativos. A maioria das propriedades usa como padrão OneWay associação, mas algumas propriedades de dependência (normalmente propriedades de controles editáveis pelo usuário, como o padrão TextBox.Text e CheckBox.IsChecked para TwoWay associação.

    Uma maneira programática de determinar se uma propriedade de dependência se vincula unidirecional ou bidirecional por padrão é obter os metadados da propriedade com DependencyProperty.GetMetadata. O tipo de retorno desse método é PropertyMetadata, que não contém metadados sobre vinculação. No entanto, se esse tipo pode ser convertido para o derivado FrameworkPropertyMetadata, então o valor booleano da FrameworkPropertyMetadata.BindsTwoWayByDefault propriedade pode ser verificado. O exemplo de código a seguir demonstra como obter os metadados para a TextBox.Text propriedade:

    public static void PrintMetadata()
    {
        // Get the metadata for the property
        PropertyMetadata metadata = TextBox.TextProperty.GetMetadata(typeof(TextBox));
    
        // Check if metadata type is FrameworkPropertyMetadata
        if (metadata is FrameworkPropertyMetadata frameworkMetadata)
        {
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:");
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}");
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}");
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}");
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}");
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}");
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}");
        }
    
        /*  Displays:
         *  
         *  TextBox.Text property metadata:
         *    BindsTwoWayByDefault: True
         *    IsDataBindingAllowed: True
         *          AffectsArrange: False
         *          AffectsMeasure: False
         *           AffectsRender: False
         *                Inherits: False
        */
    }
    
    Public Shared Sub PrintMetadata()
    
        Dim metadata As PropertyMetadata = TextBox.TextProperty.GetMetadata(GetType(TextBox))
        Dim frameworkMetadata As FrameworkPropertyMetadata = TryCast(metadata, FrameworkPropertyMetadata)
    
        If frameworkMetadata IsNot Nothing Then
    
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:")
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}")
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}")
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}")
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}")
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}")
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}")
    
            '  Displays:
            '
            '  TextBox.Text property metadata:
            '    BindsTwoWayByDefault: True
            '    IsDataBindingAllowed: True
            '          AffectsArrange: False
            '          AffectsMeasure: False
            '           AffectsRender: False
            '                Inherits: False
        End If
    
    
    End Sub
    
  • OneWayToSource é o inverso da associação OneWay; atualiza a propriedade de origem quando a propriedade de destino é alterada. Um cenário de exemplo é se você precisar reavaliar o valor de origem da interface do usuário.

  • A figura não ilustra a associação OneTime, o que faz com que a propriedade de origem inicialize a propriedade de destino, mas não propague as alterações subsequentes. Se o contexto de dados for alterado ou o objeto no contexto de dados for alterado, a alteração não será refletida na propriedade de destino. Esse tipo de associação é apropriado se um instantâneo do estado atual for apropriado ou se os dados forem realmente estáticos. Esse tipo de associação também será útil se você quiser inicializar sua propriedade de destino com algum valor de uma propriedade de origem e o contexto de dados não for conhecido com antecedência. Esse modo é essencialmente uma forma mais simples de associação OneWay que fornece melhor desempenho nos casos em que o valor de origem não é alterado.

Para detectar alterações de origem (aplicáveis a associações OneWay e TwoWay), a origem deve implementar um mecanismo de notificação de alteração de propriedade adequado, como INotifyPropertyChanged. Confira Como implementar a notificação de alteração de propriedade (.NET Framework) para obter um exemplo de uma implementação de INotifyPropertyChanged.

A propriedade Binding.Mode fornece mais informações sobre modos de associação e um exemplo de como especificar a direção de uma associação.

O que dispara atualizações de origem

As associações que são TwoWay ou OneWayToSource escutam as alterações na propriedade de destino e as propagam de volta para a origem, conhecida como atualização da origem. Por exemplo, você pode editar o texto de uma caixa de texto para alterar o valor da origem subjacente.

Entretanto, seu valor de origem é atualizado enquanto você está editando o texto ou depois que você termina de editar o texto e o controle perde o foco? A propriedade Binding.UpdateSourceTrigger determina o que dispara a atualização da origem. Os ponto das setas para a direita na figura a seguir ilustram a função da propriedade Binding.UpdateSourceTrigger.

Diagram that shows the role of the UpdateSourceTrigger property.

Se o valor UpdateSourceTrigger for UpdateSourceTrigger.PropertyChanged, o valor apontado pela seta para a direita de TwoWay ou as associações de OneWayToSource será atualizado assim que a propriedade de destino for alterada. No entanto, se o valor UpdateSourceTrigger for LostFocus, esse valor só será atualizado com o novo valor quando a propriedade de destino perder o foco.

Semelhante à propriedade Mode, propriedades de dependência diferentes têm valores de padrão UpdateSourceTrigger diferentes. O valor padrão para a maioria das propriedades de dependência é PropertyChanged, o que faz com que o valor da propriedade de origem seja alterado instantaneamente quando o valor da propriedade de destino é alterado. Alterações instantâneas são boas para CheckBox e outros controles simples. No entanto, para campos de texto, a atualização após cada pressionamento de tecla pode diminuir o desempenho e nega ao usuário a oportunidade normal de pressionar backspace e corrigir erros de digitação antes de se comprometer com o novo valor. Por exemplo, a propriedade TextBox.Text usa como padrão o valor UpdateSourceTrigger de LostFocus, o que faz com que o valor de origem seja alterado somente quando o elemento de controle perde o foco, não quando a propriedade TextBox.Text é alterada. Confira a página de propriedades UpdateSourceTrigger para obter informações sobre como encontrar o valor padrão de uma propriedade de dependência.

A tabela a seguir fornece um cenário de exemplo para cada valor UpdateSourceTrigger usando o TextBox como exemplo.

Valor de UpdateSourceTrigger Quando o valor de origem é atualizado Cenário de exemplo para TextBox
LostFocus (padrão para TextBox.Text) Quando o controle TextBox perde o foco. Um TextBox associado à lógica de validação (confira Validação de Dados abaixo).
PropertyChanged Conforme você digita no TextBox. Controles TextBox em uma janela da sala de chat.
Explicit Quando o aplicativo chama UpdateSource. Controles TextBox em um formulário editável (atualizam os valores de origem somente quando o usuário pressiona o botão enviar).

Para obter um exemplo, confira Como Controlar quando o texto TextBox atualiza a origem (.NET Framework).

Exemplo de associação de dados

Para obter um exemplo de associação de dados, dê uma olhada na interface do usuário do aplicativo a seguir na Demonstração de Vinculação de Dados, que exibe uma lista de itens de leilão.

Data binding sample screenshot

O aplicativo demonstra os seguintes recursos de associação de dados:

  • O conteúdo do ListBox está associado a uma coleção de objetos AuctionItem. Um objeto AuctionItem tem propriedades como Description, StartPrice, StartDate, Category e SpecialFeatures.

  • Os dados (objetos AuctionItem) exibidos no ListBox são modelos para que a descrição e o preço atual sejam mostrados para cada item. O modelo é criado usando um DataTemplate. Além disso, a aparência de cada item depende do valor de SpecialFeatures do AuctionItem sendo exibido. Se o valor SpecialFeatures do AuctionItem for Color, o item terá uma borda azul. Se o valor for Highlight, o item terá uma borda laranja e uma estrela. A seção Modelos de dados fornece informações sobre modelagem de dados.

  • O usuário pode agrupar, filtrar ou classificar os dados usando o CheckBoxes fornecido. Na imagem acima,CheckBoxesAgrupar por categoria e Classificar por categoria e data estão selecionados. Você talvez tenha observado que os dados estão agrupados com base na categoria do produto e que o nome da categoria está em ordem alfabética. É difícil observar da imagem, mas os itens também são classificados pela data de início dentro de cada categoria. A classificação é feita usando uma exibição de coleção. A seção Associação a coleções discute exibições de coleção.

  • Quando o usuário seleciona um item, o ContentControl exibe os detalhes do item selecionado. Essa experiência é chamada de Cenário de detalhes principais. A seção Cenário de detalhes principais fornece informações sobre esse tipo de associação.

  • O tipo da propriedade StartDate é DateTime, que retorna uma data que inclui a hora para o milissegundo. Neste aplicativo, um conversor personalizado foi usado para que uma cadeia de caracteres de data mais curta seja exibida. A seção Conversão de dados fornece informações sobre conversores.

Quando o usuário seleciona o botão Adicionar Produto, o formulário a seguir aparece.

Add Product Listing page

O usuário pode editar os campos no formulário, visualizar a listagem do produto usando os painéis de visualização curtos ou detalhados e selecionar Submit para adicionar a nova listagem de produtos. Todas as configurações de agrupamento, filtragem e classificação existentes serão aplicadas à nova entrada. Neste caso em particular, o item inserido na imagem acima será exibido como o segundo item dentro da categoria Computador.

Esta imagem não mostra a lógica de validação fornecida na Data de InícioTextBox. Se o usuário inserir uma data inválida (formatação inválida ou uma data passada), o usuário será notificado com um ToolTip e um ponto de exclamação vermelho ao lado do TextBox. A seção Validação de dados discute como criar lógica de validação.

Antes de entrar nos diferentes recursos de associação de dados descritos acima, primeiro discutiremos os conceitos fundamentais que são cruciais para entender a associação de dados do WPF.

Criar uma associação

Para reafirmar alguns dos conceitos discutidos nas seções anteriores, você estabelece uma associação usando o objeto Binding e cada associação geralmente tem quatro componentes: um destino de associação, uma propriedade de destino, uma origem de associação e um caminho para o valor de origem a ser usado. Esta seção discute como configurar uma associação.

As fontes de associação estão vinculadas à DataContext ativa do elemento. Os elementos herdam automaticamente seus DataContext se não tiverem definido explicitamente um.

Considere o exemplo a seguir, no qual o objeto de origem da associação é uma classe chamada MyData que é definida no namespace SDKSample. Para fins de demonstração, MyData tem uma propriedade de cadeia de caracteres chamada ColorName cujo valor é definido como "Vermelho". Assim, esse exemplo gera um botão com uma tela de fundo vermelha.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Para obter mais informações sobre a sintaxe da declaração de associação e exemplos de como configurar uma associação no código, confira a Visão geral das declarações de associação.

Se nós aplicarmos esse exemplo a nosso diagrama básico, a figura resultante será semelhante à exibida a seguir. Essa figura descreve uma associação de OneWay porque a propriedade Background dá suporte a OneWay associação por padrão.

Diagram that shows the data binding Background property.

Você pode se perguntar por que essa associação funciona mesmo que a propriedade ColorName seja do tipo cadeia de caracteres enquanto a propriedade Background é do tipo Brush. Essa associação usa a conversão de tipo padrão, que é discutida na seção Conversão de dados.

Especificar a origem da associação

Observe que, no exemplo anterior, a fonte de associação é especificada definindo a propriedade DockPanel.DataContext. O Button herda o valor DataContext do DockPanel, que é seu elemento pai. Para reiterar, o objeto de origem da associação é um dos quatro componentes necessários de uma associação. Portanto, sem que o objeto de origem de associação fosse especificado, a associação não faria nada.

Há várias maneiras para especificar o objeto de origem de associação. Usar a propriedade DataContext em um elemento pai é útil quando você está associando várias propriedades à mesma origem. No entanto, às vezes pode ser mais apropriado especificar a origem da associação em declarações de associação individuais. Para o exemplo anterior, em vez de usar a propriedade DataContext, você pode especificar a origem da associação definindo a propriedade Binding.Source diretamente na declaração de associação do botão, como no exemplo a seguir.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Além de definir a propriedade DataContext em um elemento diretamente, herdar o valor DataContext de um ancestral (como o botão no primeiro exemplo) e especificar explicitamente a origem da associação definindo a propriedade Binding.Source na associação (como o botão do último exemplo), você também pode usar a propriedade Binding.ElementName ou a propriedade Binding.RelativeSource para especificar a origem da associação. A propriedade ElementName é útil quando você está associando a outros elementos em seu aplicativo, como quando você está usando um controle deslizante para ajustar a largura de um botão. A propriedade RelativeSource é útil quando a associação é especificada em um ControlTemplate ou um Style. Para obter mais informações, confira a Visão geral das fontes de associação.

Especificar o caminho para o valor

Se a origem da associação for um objeto, você usará a propriedade Binding.Path para especificar o valor a ser usado para sua associação. Se você estiver associando dados XML, use a propriedade Binding.XPath para especificar o valor. Em alguns casos, pode ser aplicável usar a propriedade Path mesmo quando seus dados são XML. Por exemplo, se você quiser acessar a propriedade Name de um XmlNode retornado (como resultado de uma consulta XPath), deverá usar a propriedade Path além da propriedade XPath.

Para obter mais informações, confira as propriedades Path e XPath.

Embora tenhamos enfatizado que o Path ao valor a ser usado é um dos quatro componentes necessários de uma associação, nos cenários que você deseja associar a um objeto inteiro, o valor a ser usado seria o mesmo que o objeto de origem de associação. Nesses casos, é aplicável não especificar um Path. Considere o exemplo a seguir.

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

O exemplo acima usa a sintaxe de associação vazia: {Binding}. Nesse caso, o ListBox herda o DataContext de um elemento DockPanel pai (não mostrado neste exemplo). Quando o caminho não é especificado, o padrão é associar ao objeto inteiro. Em outras palavras, neste exemplo, o caminho foi deixado de fora porque estamos associando a propriedade ItemsSource ao objeto inteiro. (Confira a seção Associação a coleções para uma discussão detalhada.)

Além de ao associar a uma coleção, este cenário também é útil quando você deseja associar a um objeto inteiro em vez de apenas uma única propriedade de um objeto. Por exemplo, se o objeto de origem for do tipo String, você pode simplesmente querer associar-se à cadeia de caracteres em si. Outro cenário comum é quando você deseja associar um elemento a um objeto com várias propriedades.

Talvez seja necessário aplicar a lógica personalizada para que os dados sejam significativos à sua propriedade de destino associada. A lógica personalizada pode estar na forma de um conversor personalizado se a conversão de tipo padrão não existir. Confira Conversão de dados para obter informações sobre conversores.

Associação e BindingExpression

Antes de entrar em outros recursos e usos da associação de dados, é útil introduzir a classe BindingExpression. Como você viu em seções anteriores, a classe Binding é a classe de alto nível para a declaração de uma associação; ela fornece muitas propriedades que permitem especificar as características de uma associação. Uma classe relacionada, BindingExpression, é o objeto subjacente que mantém a conexão entre a origem e o destino. Uma associação contém todas as informações que podem ser compartilhadas entre várias expressões de associação. Um BindingExpression é uma expressão de instância que não pode ser compartilhada e contém todas as informações de instância do Binding.

Considere o exemplo a seguir, em que myDataObject é instância da classe MyData, myBinding é o objeto Binding de origem e MyData é uma classe definida que contém uma propriedade de cadeia de caracteres chamada ColorName. Este exemplo associa o conteúdo de texto de myText, uma instância de TextBlock, a ColorName.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

Você pode usar o mesmo objeto myBinding para criar outras associações. Por exemplo, você pode usar o objeto myBinding para associar o conteúdo do texto de uma caixa de seleção ao ColorName. Nesse cenário, haverá duas instâncias de BindingExpressioncompartilhando o objeto myBinding.

Um objeto BindingExpression é retornado chamando GetBindingExpression em um objeto associado a dados. Os artigos a seguir demonstram alguns dos usos da classe BindingExpression:

Conversão de dados

Na seção Criar uma associação, o botão é vermelho porque sua propriedade Background está associada a uma propriedade de cadeia de caracteres com o valor "Red". Esse valor de cadeia de caracteres funciona porque um conversor de tipo está presente no tipo Brush para converter o valor da cadeia de caracteres em um Brush.

A adição dessas informações à figura na seção Criar uma associação é semelhante a esta.

Diagram that shows the data binding Default property.

No entanto, e se, em vez de ter uma propriedade de cadeia de caracteres de tipo, seu objeto de origem de associação tiver uma propriedade Cor do tipo Color? Nesse caso, para que a associação funcione, você precisaria primeiro transformar o valor da propriedade Cor em algo que a propriedade Background aceita. Você precisaria criar um conversor personalizado implementando a interface IValueConverter, como no exemplo a seguir.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Consulte IValueConverter para obter mais informações.

Agora, o conversor personalizado é usado em vez de conversão padrão, e nosso diagrama tem esta aparência.

Diagram that shows the data binding custom converter.

Para reiterar, conversões padrão podem estar disponíveis devido a conversores de tipo presentes no tipo para o qual a associação está sendo realizada. Esse comportamento dependerá de quais conversores de tipo estão disponíveis no destino. Em caso de dúvida, crie seu próprio conversor.

Veja a seguir alguns cenários típicos em que faz sentido implementar um conversor de dados:

  • Os dados devem ser exibidos de forma diferente, dependendo da cultura. Por exemplo, talvez você queira implementar um conversor de moeda ou um conversor de data/hora do calendário com base nas convenções usadas em uma cultura específica.

  • Os dados que estão sendo usados não se destinam necessariamente a alterar o valor de texto de uma propriedade, mas se destinam a alterar algum outro valor, como a origem de uma imagem, ou a cor ou o estilo do texto de exibição. Conversores podem ser usados nesta instância convertendo a associação de uma propriedade que pode não parecer apropriada, assim como a associação de um campo de texto à propriedade Background de uma célula de tabela.

  • Mais de um controle ou várias propriedades de controles estão associados aos mesmos dados. Nesse caso, a associação primária pode simplesmente exibir o texto, enquanto outras associações lidam com questões específicas de exibição, mas ainda usam a mesma associação como informações da origem.

  • Uma propriedade de destino tem uma coleção de associações, que é denominada MultiBinding. Para MultiBinding, use um IMultiValueConverter personalizado para produzir um valor final dos valores das associações. Por exemplo, a cor pode ser computada de valores vermelhos, azuis e verdes, que podem ser valores dos mesmos ou de diferentes objetos de origem da associação. Confira MultiBinding para obter exemplos e informações.

Associação a coleções

Um objeto de origem de associação pode ser tratado como um único objeto cujas propriedades contêm dados ou como uma coleção de dados de objetos polimórficos que geralmente são agrupados (como o resultado de uma consulta a um banco de dados). Até agora, discutimos apenas a associação a objetos únicos. No entanto, a associação a uma coleta de dados é um cenário comum. Por exemplo, um cenário comum é usar um ItemsControl como um ListBox, ListView ou TreeView para exibir uma coleção de dados, como no aplicativo mostrado na seção O que é associação de dados.

Felizmente, nosso diagrama básico ainda se aplica. Se você estiver associando um ItemsControl a uma coleção, o diagrama terá esta aparência.

Diagram that shows the data binding ItemsControl object.

Conforme mostrado neste diagrama, para associar um ItemsControl a um objeto de coleção, a propriedade ItemsControl.ItemsSource é a propriedade a ser usada. Você pode pensar em ItemsSource como o conteúdo do ItemsControl. A associação é OneWay porque a propriedade ItemsSource dá suporte à associação OneWay por padrão.

Como implementar coleções

Você pode enumerar em qualquer coleção que implemente a interface IEnumerable. No entanto, para configurar associações dinâmicas para que inserções ou exclusões na coleção atualizem a interface do usuário automaticamente, a coleção deve implementar a interface INotifyCollectionChanged. Essa interface expõe um evento que deve ser acionado sempre que a coleção subjacente for alterada.

O WPF fornece a classe ObservableCollection<T>, que é uma implementação interna de uma coleção de dados que expõe a interface INotifyCollectionChanged. Para dar suporte total à transferência de valores de dados de objetos de origem para destinos, cada objeto em sua coleção que dá suporte a propriedades associáveis também deve implementar a interface INotifyPropertyChanged. Para obter mais informações, confira a Visão geral das fontes de associação.

Antes de implementar sua própria coleção, considere usar ObservableCollection<T> ou uma das classes de coleção existentes, como List<T>, Collection<T> e BindingList<T>, entre muitas outras. Se você tiver um cenário avançado e quiser implementar sua própria coleção, considere usar IList, que fornece uma coleção não genérica de objetos que podem ser acessados individualmente pelo índice e, portanto, fornece o melhor desempenho.

Exibições de coleção

Depois que o ItemsControl estiver associado a uma coleta de dados, convém classificar, filtrar ou agrupar os dados. Para fazer isso, você usa exibições de coleção, que são classes que implementam a interface ICollectionView.

O que são exibições de coleção?

Uma exibição de coleção é uma camada sobre uma coleção de origem da associação que permite a você navegar e exibir a coleção de origem com base em consultas de classificação, filtragem e agrupamento, sem precisar alterar a coleção de origem subjacente. Uma exibição de coleção também mantém um ponteiro para o item atual na coleção. Se a coleção de origem implementar a interface INotifyCollectionChanged, as alterações geradas pelo evento CollectionChanged serão propagadas para os modos de exibição.

Já que as exibições não alteram as coleções de origem subjacentes, cada coleção de origem pode ter várias exibições associadas a ela. Por exemplo, você pode ter uma coleção de objetos Task. Com o uso de exibições, você pode exibir esses mesmos dados de maneiras diferentes. Por exemplo, no lado esquerdo da página, talvez você queira mostrar as tarefas ordenadas por prioridade e, no lado direito, agrupadas por área.

Como criar uma exibição

Uma maneira de criar e usar uma exibição é instanciar o objeto de exibição diretamente e então usá-lo como a origem da associação. Por exemplo, considere o aplicativo de demonstração de vinculação de dados mostrado na seção O que é vinculação de dados. O aplicativo é implementado de modo que o ListBox se associe a uma exibição sobre a coleta de dados em vez da coleta de dados diretamente. O exemplo a seguir é extraído do aplicativo de demonstração de vinculação de dados. A classe CollectionViewSource é o proxy XAML de uma classe que herda de CollectionView. Neste exemplo específico, o Source do modo de exibição está associado à coleção AuctionItems (do tipo ObservableCollection<T>) do objeto de aplicativo atual.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

O recurso listingDataView serve então como a origem da associação para elementos do aplicativo, tais como a ListBox.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

Para criar outra exibição para a mesma coleção, você pode criar outra instância CollectionViewSource e dar a ela um nome x:Key diferente.

A tabela a seguir mostra quais tipos de dados de exibição são criados como a exibição de coleção padrão ou CollectionViewSource com base no tipo de coleta de origem.

Tipo de coleção de origem Tipo de exibição de coleção Observações
IEnumerable Um tipo interno baseado em CollectionView Não é possível agrupar itens.
IList ListCollectionView Mais rápido.
IBindingList BindingListCollectionView

Usar uma exibição padrão

Especificar uma exibição de coleção como uma origem da associação é uma maneira de criar e usar uma exibição de coleção. O WPF também cria uma exibição de coleção padrão para cada coleção usada como uma origem da associação. Se você associar diretamente a uma coleção, o WPF será associado à sua exibição padrão. Essa exibição padrão é compartilhada por todas as associações à mesma coleção, portanto, uma alteração feita em um modo de exibição padrão por um controle ou código associado (como classificação ou alteração no ponteiro do item atual, discutido posteriormente) é refletida em todas as outras associações para a mesma coleção.

Para obter a exibição padrão, use o método GetDefaultView. Para obter um exemplo, confira Obter a exibição padrão de uma coleção de dados (.NET Framework).

Exibições de coleção com ADO.NET DataTables

Para melhorar o desempenho, as exibições de coleção para objetos ADO.NET DataTable ou DataView delegam a classificação e a filtragem para o DataView, o que faz com que a classificação e a filtragem sejam compartilhadas em todas as exibições de coleção da fonte de dados. Para permitir que cada exibição de coleção classifique e filtre de forma independente, inicialize cada exibição de coleção com seu próprio objeto DataView.

Classificação

Conforme mencionado anteriormente, as exibições podem aplicar uma ordem de classificação para uma coleção. Já que eles existem na coleção subjacente, seus dados podem ter ou não uma ordem inerente e relevante. A exibição da coleção permite impor uma ordem ou alterar a ordem padrão, com base em critérios de comparação fornecidos por você. Como é uma exibição baseada no cliente dos dados, um cenário comum é que o usuário pode querer classificar colunas de dados tabulares de acordo com o valor ao qual a coluna corresponde. Usando exibições, essa classificação controlada pelo usuário pode ser aplicada, novamente sem necessidade de alterações na coleção subjacente nem mesmo precisar repetir a consulta do conteúdo da coleção. Para obter um exemplo, confira Classificar uma coluna GridView quando um cabeçalho for clicado (.NET Framework).

O exemplo a seguir mostra a lógica de classificação do CheckBox "Classificar por categoria e data" da interface do usuário do aplicativo na seção O que é associação de dados.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

Filtragem

As exibições também podem aplicar um filtro a uma coleção, de modo que a exibição mostre apenas um determinado subconjunto da coleção completa. Você pode filtrar por uma condição nos dados. Por exemplo, como é feito pelo aplicativo na seção O que é associação de dados, a "Mostrar apenas ofertas"CheckBox contém lógica para filtrar itens que custam US$ 25 ou mais. O código a seguir é executado para definir ShowOnlyBargainsFilter como o manipulador de eventos Filter quando esse CheckBox é selecionado.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

O manipulador ShowOnlyBargainsFilter tem a seguinte implementação.

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

Se você estiver usando uma das classes CollectionView diretamente em vez de CollectionViewSource, usará a propriedade Filter para especificar um retorno de chamada. Para obter um exemplo, confira Filtrar Dados em uma Exibição (.NET Framework).

Agrupamento

Exceto pela classe interna que exibe uma coleção IEnumerable, todas as exibições de coleção dão suporte ao agrupamento, o que permite ao usuário particionar a coleção na exibição de coleção em grupos lógicos. Os grupos podem ser explícitos (caso em que o usuário fornece uma lista de grupos) ou então implícitos (caso em que os grupos são gerados dinamicamente, dependendo dos dados).

O exemplo a seguir mostra a lógica do"Agrupar por categoria" CheckBox.

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

Para outro exemplo de agrupamento, confira Itens de Grupo em um ListView que implementa um GridView (.NET Framework).

Ponteiros de item atuais

As exibições também dão suporte à noção de um item atual. Você pode navegar pelos objetos em uma exibição de coleção. Ao navegar, você está movendo um ponteiro de item que permite recuperar o objeto que existe nesse local específico na coleção. Para obter um exemplo, confira Navegar pelos objetos em um CollectionView de dados (.NET Framework).

Já que o WPF associa a uma coleção usando uma exibição (uma exibição que você especificar ou uma exibição padrão da coleção), todas as associações a coleções têm um ponteiro de item atual. Ao associar a um modo de exibição, o caractere de barra ("/") em um valor Path atribui o item atual da exibição. No exemplo a seguir, o contexto de dados é uma exibição de coleção. A primeira linha é associada à coleção. A segunda linha é associada ao item atual na coleção. A terceira linha é associada à propriedade Description do item atual na coleção.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

A sintaxe de barra e de propriedade também pode ser empilhada para transpor uma hierarquia de coleções. O exemplo a seguir associa o item atual de uma coleção chamada Offices, que é uma propriedade do item atual da coleção de origem.

<Button Content="{Binding /Offices/}" />

O ponteiro do item atual pode ser afetado por qualquer classificação ou filtragem aplicada à coleção. A classificação preserva o ponteiro do item atual no último item selecionado, mas a exibição de coleção está agora reestruturada em torno dele. (Talvez o item selecionado estivesse no início da lista antes, mas agora o item selecionado pode estar em algum lugar no meio.) A filtragem preservará o item selecionado se essa seleção permanecer em exibição após a filtragem. Caso contrário, o ponteiro do item atual será definido como o primeiro item da exibição de coleção filtrada.

Cenário de associação principal-detalhe

A noção de um item atual é útil não somente para navegação por itens em uma coleção, mas também para o cenário de associação mestre/detalhes. Considere a interface do usuário do aplicativo na seção O que é associação de dados novamente. Nesse aplicativo, a seleção no ListBox determina o conteúdo mostrado no ContentControl. Para colocá-lo de outra forma, quando um item de ListBox é selecionado, o ContentControl mostra os detalhes do item selecionado.

Você pode implementar o cenário mestre/detalhes simplesmente associando dois ou mais controles à mesma exibição. O exemplo a seguir da demonstração de vinculação de dados mostra a marcação do e o ContentControl que você vê na interface do usuário do aplicativo na seção O que é vinculação de ListBox dados.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

Observe que ambos os controles estão associados à mesma origem, o recurso estático listingDataView (confira a definição desse recurso na seção Como criar uma exibição). Essa associação funciona porque quando um objeto singleton (o ContentControl nesse caso) está associado a um modo de exibição de coleção, ele se associa automaticamente ao CurrentItem do modo de exibição. Os objetos CollectionViewSource sincronizam automaticamente a moeda e a seleção. Se o controle de lista não estiver associado a um objeto CollectionViewSource como neste exemplo, você precisará definir sua propriedade IsSynchronizedWithCurrentItem para true que isso funcione.

Para outros exemplos, confira Associar a uma coleção e exibir informações com base na seleção (.NET Framework) e Usar o padrão de detalhes mestre com dados hierárquicos (.NET Framework).

Talvez você tenha observado que o exemplo anterior usa um modelo. Na verdade, os dados não seriam exibidos da maneira desejada sem o uso de modelos (aquele explicitamente usado pelo ContentControl e aquele usado implicitamente pelo ListBox). Agora, na próxima seção, nos ocuparemos da modelagem de dados.

Modelagem de dados

Sem o uso de modelos de dados, a interface do usuário do aplicativo na seção Exemplo de associação de dados seria semelhante à seguinte:

Data Binding Demo without Data Templates

Conforme mostrado no exemplo na seção anterior, o controle ListBox e o ContentControl são associados a todo o objeto de coleção (ou mais especificamente, à exibição sobre o objeto de coleção) de AuctionItems. Sem instruções específicas de como exibir a coleção de dados, o ListBox exibe a representação de cadeia de caracteres de cada objeto na coleção subjacente e o ContentControl exibe a representação de cadeia de caracteres do objeto ao qual está associado.

Para resolver esse problema, o aplicativo define DataTemplates. Conforme mostrado no exemplo na seção anterior, o ContentControl usa explicitamente o modelo de dados detailsProductListingTemplate. O controle ListBox usa implicitamente o modelo de dados a seguir ao exibir os objetos AuctionItem na coleção.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Com o uso desses dois DataTemplates, a interface do usuário resultante é a mostrada na seção O que é associação de dados. Como você pode ver nessa captura de tela, além de permitir que você coloque dados em seus controles, o DataTemplates permite que você defina visuais atraentes para seus dados. Por exemplo, DataTriggers são usados na DataTemplate acima para que AuctionItemcom o valor SpecialFeatures de HighLight seja exibido com uma borda laranja e uma estrela.

Para obter mais informações sobre modelos de dados, confira a Visão geral de modelagem de dados (.NET Framework).

Validação de dados

A maioria dos aplicativos que tomam a entrada do usuário precisa ter lógica de validação para garantir que o usuário tenha inserido as informações esperadas. As verificações de validação podem ser baseadas no tipo, intervalo, formato ou outros requisitos específicos do aplicativo. Esta seção discute como a validação de dados funciona no WPF.

Associar regras de validação a uma associação

O modelo de associação de dados do WPF permite associar ValidationRules ao objeto Binding. Por exemplo, o exemplo a seguir associa um TextBox a uma propriedade chamada StartPrice e adiciona um objeto ExceptionValidationRule à propriedade Binding.ValidationRules.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Um objeto ValidationRule verifica se o valor de uma propriedade é válido. O WPF tem dois tipos de objetos ValidationRule internos:

Você também pode criar sua própria regra de validação derivando da classe ValidationRule e implementando o método Validate. O exemplo a seguir mostra a regra usada pela Adicionar Lista de Produtos "Data de Início" TextBox da seção O que é associação de dados.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

O TextBox StartDateEntryForm usa este FutureDateRule, conforme mostrado no exemplo a seguir.

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Como o valor UpdateSourceTrigger é PropertyChanged, o mecanismo de associação atualiza o valor de origem em cada pressionamento de tecla, o que significa que ele também verifica todas as regras da coleção ValidationRules em cada pressionamento de tecla. Discutiremos isso de modo mais aprofundado na seção Processo de validação.

Fornecer comentários visuais

Se o usuário inserir um valor inválido, talvez você queira fornecer alguns comentários sobre o erro na interface do usuário do aplicativo. Uma maneira de fornecer esses comentários é definir a propriedade anexada Validation.ErrorTemplate a um ControlTemplatepersonalizado. Conforme mostrado na subseção anterior, oTextBoxStartDateEntryForm usa um ErrorTemplate chamado validationTemplate. O exemplo a seguir mostra a definição de validationTemplate.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

O elemento AdornedElementPlaceholder especifica onde o controle que está sendo adornado deve ser colocado.

Além disso, você também pode usar um ToolTip para exibir a mensagem de erro. Tanto StartDateEntryForm quanto StartPriceEntryFormTextBoxes usam o estilo textStyleTextBox, que cria um ToolTip que exibe a mensagem de erro. O exemplo a seguir mostra a definição de textStyleTextBox. A propriedade anexada Validation.HasError é true quando uma ou mais das associações nas propriedades do elemento associado estão em erro.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Com o ErrorTemplate personalizado e o ToolTip, oTextBoxStartDateEntryForm é semelhante ao seguinte quando há um erro de validação.

Data binding validation error for date

Se o Binding tiver regras de validação associadas, mas você não especificar um ErrorTemplate no controle associado, um ErrorTemplate padrão será usado para notificar os usuários quando houver um erro de validação. O ErrorTemplate padrão é um modelo de controle que define uma borda vermelha na camada do adorno. Com o ErrorTemplate padrão e o ToolTip, a interface do usuário do TextBoxStartPriceEntryForm é semelhante à seguinte quando há um erro de validação.

Data binding validation error for price

Para obter um exemplo de como fornecer lógica para validar todos os controles em uma caixa de diálogo, confira a seção Caixas de Diálogo Personalizadas na Visão geral das caixas de diálogo.

Processo de validação

Geralmente, a validação ocorre quando o valor de um destino é transferido para a propriedade de origem da associação. Essa transferência ocorre em associações TwoWay e OneWayToSource. Para reiterar, o que causa uma atualização de origem depende do valor da propriedade UpdateSourceTrigger, conforme descrito na seção O que dispara atualizações de origem.

Os itens a seguir descrevem o processo de validação. Se ocorrer um erro de validação ou outro tipo de erro a qualquer momento durante esse processo, o processo será interrompido:

  1. O mecanismo de associação verifica se há algum ValidationRule personalizado definido cujo ValidationStep está definido como RawProposedValue para esse Binding, nesse caso, ele chama o método Validate em cada ValidationRule até que um deles gera um erro ou até que todos eles passem.

  2. O mecanismo de associação então chamará o conversor, se houver.

  3. Se o conversor for bem-sucedido, o mecanismo de associação verificará se há algum objeto ValidationRule personalizado definido cujo ValidationStep está definido como ConvertedProposedValue para esse Binding, nesse caso, ele chama o método Validate em cada ValidationRule que ValidationStep definido como ConvertedProposedValue até que um deles gere um erro ou até que todos eles passem.

  4. O mecanismo de associação define a propriedade de origem.

  5. O mecanismo de associação verifica se há algum ValidationRule personalizado definido cujo ValidationStep está definido como UpdatedValue para esse Binding, nesse caso, ele chama o método Validate em cada ValidationRule que ValidationStep definido como UpdatedValue até que um deles gere um erro ou até que todos eles passem. Se um DataErrorValidationRule estiver associado a uma associação e seu ValidationStep estiver definido como o padrão, UpdatedValue, o DataErrorValidationRule será verificado neste momento. Neste ponto, qualquer associação que tenha a ValidatesOnDataErrors definida como true está marcada.

  6. O mecanismo de associação verifica se há algum ValidationRule personalizado definido cujo ValidationStep está definido como CommittedValue para esse Binding, nesse caso, ele chama o método Validate em cada ValidationRule que ValidationStep definido como CommittedValue até que um deles gere um erro ou até que todos eles passem.

Se um ValidationRule não passar em nenhum momento durante esse processo, o mecanismo de associação criará um objeto ValidationError e o adicionará à coleção Validation.Errors do elemento associado. Antes que o mecanismo de associação execute os objetos ValidationRule em qualquer etapa, ele remove qualquer ValidationError que foi adicionada à propriedade anexada Validation.Errors do elemento associado durante essa etapa. Por exemplo, se um ValidationRule cujo ValidationStep estiver definido como UpdatedValue falhar, na próxima vez que o processo de validação ocorrer, o mecanismo de associação removerá esse ValidationError imediatamente antes de chamar qualquer ValidationRule que tenha ValidationStep definido como UpdatedValue.

Quando Validation.Errors não está vazio, a propriedade anexada Validation.HasError do elemento é definida como true. Além disso, se a propriedade NotifyOnValidationError do Binding estiver definida como true, o mecanismo de associação gerará o evento anexado Validation.Error no elemento.

Observe também que uma transferência de valor válida em qualquer direção (destino para origem ou origem para destino) limpa a propriedade anexada Validation.Errors.

Se a associação tiver uma ExceptionValidationRule associada a ela ou se a propriedade ValidatesOnExceptions estiver definida como true e uma exceção for gerada quando o mecanismo de associação definir a origem, o mecanismo de associação verificará se há um UpdateSourceExceptionFilter. Você pode usar o retorno de chamada UpdateSourceExceptionFilter para fornecer um manipulador personalizado para lidar com exceções. Se um UpdateSourceExceptionFilter não for especificado no Binding, o mecanismo de associação criará um ValidationError com a exceção e o adicionará à coleção Validation.Errors do elemento associado.

Mecanismo de depuração

Você pode definir a propriedade anexada PresentationTraceSources.TraceLevel em um objeto relacionado à associação para receber informações sobre o status de uma associação específica.

Confira também