Partilhar via


Vinculação de dados em profundidade

APIs importantes

Observação

Este tópico descreve os recursos de vinculação de dados em detalhes. Para uma breve introdução prática, consulte Visão geral da vinculação de dados.

Este tópico trata da associação de dados para as APIs que residem no namespace Windows.UI.Xaml.Data.

A associação de dados é uma maneira de a interface do usuário do seu aplicativo exibir dados e, opcionalmente, permanecer sincronizada com esses dados. A associação de dados permite separar a preocupação dos dados da preocupação da interface do usuário, e isso resulta em um modelo conceitual mais simples, bem como melhor legibilidade, estabilidade e manutenção do seu aplicativo.

Você pode usar a associação de dados para simplesmente exibir valores de uma fonte de dados quando a interface do usuário é mostrada pela primeira vez, mas não para responder a alterações nesses valores. Este é um modo de vinculação chamado one-time e funciona bem para um valor que não muda durante o tempo de execução. Como alternativa, você pode optar por "observar" os valores e atualizar a interface do usuário quando eles forem alterados. Esse modo é chamado de unidirecional e funciona bem para dados somente leitura. Em última análise, você pode optar por observar e atualizar, para que as alterações feitas pelo usuário nos valores na interface do usuário sejam automaticamente enviadas de volta para a fonte de dados. Esse modo é chamado de bidirecional e funciona bem para dados de leitura-gravação. Eis alguns exemplos.

  • Você pode usar o modo único para vincular uma imagem à foto do usuário atual.
  • Você pode usar o modo unidirecional para vincular um ListView a uma coleção de artigos de notícias em tempo real agrupados por seção de jornal.
  • Você pode usar o modo bidirecional para vincular um TextBox ao nome de um cliente em um formulário.

Independentemente do modo, há dois tipos de vinculação, e ambos são normalmente declarados na marcação da interface do usuário. Você pode optar por usar a extensão de marcação {x:Bind} ou a extensão de marcação {Binding}. E você pode até usar uma mistura dos dois no mesmo aplicativo, mesmo no mesmo elemento da interface do usuário. {x:Bind} é novo para o Windows 10 e tem melhor desempenho. Todos os detalhes descritos neste tópico aplicam-se a ambos os tipos de vinculação, a menos que digamos explicitamente o contrário.

Aplicativos de exemplo que demonstram {x:Bind}

Aplicativos de exemplo que demonstram {Binding}

Toda encadernação envolve essas peças

  • Uma fonte de ligação. Essa é a fonte dos dados para a associação e pode ser uma instância de qualquer classe que tenha membros cujos valores você deseja exibir em sua interface do usuário.
  • Um alvo vinculativo. Este é um DependencyProperty do FrameworkElement em sua interface do usuário que exibe os dados.
  • Um objeto de ligação. Esta é a parte que transfere valores de dados da origem para o destino e, opcionalmente, do destino de volta para a origem. O objeto de vinculação é criado no tempo de carregamento XAML a partir da sua extensão de marcação {x:Bind} ou {Binding} .

Nas seções a seguir, examinaremos mais detalhadamente a origem da vinculação, o destino da vinculação e o objeto de vinculação. E vamos vincular as seções junto com o exemplo de vinculação do conteúdo de um botão a uma propriedade string chamada NextButtonText, que pertence a uma classe chamada HostViewModel.

Fonte de ligação

Aqui está uma implementação muito rudimentar de uma classe que poderíamos usar como uma fonte de ligação.

Se você estiver usando C++/WinRT, adicione novos itens Midl File (.idl) ao projeto, nomeados como mostrado na lista de exemplo de código C++/WinRT abaixo. Substitua o conteúdo desses novos arquivos pelo código MIDL 3.0 mostrado na listagem, crie o projeto para gerar HostViewModel.h e .cppe, em seguida, adicione código aos arquivos gerados para corresponder à listagem. Para obter mais informações sobre esses arquivos gerados e como copiá-los em seu projeto, consulte Controles XAML; vincular a uma propriedade C++/WinRT.

public class HostViewModel
{
    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Implement the constructor like this, and add this field:
...
HostViewModel() : m_nextButtonText{ L"Next" } {}
...
private:
    std::wstring m_nextButtonText;
...

// HostViewModel.cpp
// Implement like this:
...
hstring HostViewModel::NextButtonText()
{
    return hstring{ m_nextButtonText };
}

void HostViewModel::NextButtonText(hstring const& value)
{
    m_nextButtonText = value;
}
...

Essa implementação de HostViewModel, e sua propriedade NextButtonText, são apropriadas apenas para vinculação única. Mas as ligações unidirecionais e bidirecionais são extremamente comuns e, nesses tipos de ligação, a interface do usuário é atualizada automaticamente em resposta a alterações nos valores de dados da fonte de vinculação. Para que esses tipos de vinculação funcionem corretamente, você precisa tornar sua fonte de vinculação "observável" para o objeto de vinculação. Portanto, em nosso exemplo, se quisermos vincular unidirecional ou bidirecional à propriedade NextButtonText , todas as alterações que acontecerem em tempo de execução no valor dessa propriedade precisarão ser observáveis para o objeto de vinculação.

Uma maneira de fazer isso é derivar a classe que representa sua fonte de associação de DependencyObject e expor um valor de dados por meio de um DependencyProperty. É assim que um FrameworkElement se torna observável. FrameworkElements são boas fontes de vinculação prontas para uso.

Uma maneira mais leve de tornar uma classe observável — e necessária para classes que já têm uma classe base — é implementar System.ComponentModel.INotifyPropertyChanged. Isso realmente envolve apenas a implementação de um único evento chamado PropertyChanged. Um exemplo usando HostViewModel está abaixo.

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set
        {
            this.nextButtonText = value;
            this.OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Add this field:
...
    winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
    void PropertyChanged(winrt::event_token const& token) noexcept;

private:
    winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
...

// HostViewModel.cpp
// Implement like this:
...
void HostViewModel::NextButtonText(hstring const& value)
{
    if (m_nextButtonText != value)
    {
        m_nextButtonText = value;
        m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"NextButtonText" });
    }
}

winrt::event_token HostViewModel::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
    return m_propertyChanged.add(handler);
}

void HostViewModel::PropertyChanged(winrt::event_token const& token) noexcept
{
    m_propertyChanged.remove(token);
}
...

Agora, a propriedade NextButtonText é observável. Quando você cria uma associação unidirecional ou bidirecional para essa propriedade (mostraremos como mais tarde), o objeto de vinculação resultante se inscreve no evento PropertyChanged . Quando esse evento é gerado, o manipulador do objeto de vinculação recebe um argumento contendo o nome da propriedade que foi alterada. É assim que o objeto de vinculação sabe qual o valor da propriedade a ser lido novamente.

Para que você não precise implementar o padrão mostrado acima várias vezes, se você estiver usando C#, então você pode simplesmente derivar da classe base BindableBase que você encontrará no exemplo QuizGame (na pasta "Comum"). Aqui está um exemplo de como isso parece.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set { this.SetProperty(ref this.nextButtonText, value); }
    }
}
// Your BindableBase base class should itself derive from Windows::UI::Xaml::DependencyObject. Then, in HostViewModel.idl, derive from BindableBase instead of implementing INotifyPropertyChanged.

Observação

Para C++/WinRT, qualquer classe de tempo de execução que você declarar em seu aplicativo que deriva de uma classe base é conhecida como uma classe composable . E há restrições em torno de classes compostáveis. Para que um aplicativo passe nos testes do Kit de Certificação de Aplicativos Windows usados pelo Visual Studio e pela Microsoft Store para validar envios (e, portanto, para que o aplicativo seja ingerido com êxito na Microsoft Store), uma classe composable deve, em última análise, derivar de uma classe base do Windows. O que significa que a classe na raiz da hierarquia de herança deve ser um tipo originário de um namespace Windows.*. Se você precisar derivar uma classe de tempo de execução de uma classe base — por exemplo, para implementar uma classe BindableBase para todos os seus modelos de exibição derivar — então você pode derivar de Windows.UI.Xaml.DependencyObject.

Gerar o evento PropertyChanged com um argumento de String.Empty ou null indica que todas as propriedades não indexadoras no objeto devem ser relidas. Você pode gerar o evento para indicar que as propriedades do indexador no objeto foram alteradas usando um argumento de "Item[indexer]" para indexadores específicos (onde indexador é o valor do índice) ou um valor de "Item[]" para todos os indexadores.

Uma fonte de associação pode ser tratada como um único objeto cujas propriedades contêm dados ou como uma coleção de objetos. No código C# e Visual Basic, você pode ligar uma vez a um objeto que implementa List(Of T) para exibir uma coleção que não muda em tempo de execução. Para uma coleção observável (observando quando os itens são adicionados e removidos da coleção), vincule-se unidirecional a ObservableCollection(Of T). No código C++/CX, você pode vincular ao Vetor<T> para coleções observáveis e não observáveis, e C++/WinRT tem seus próprios tipos. Para vincular às suas próprias classes de coleção, use as orientações na tabela a seguir.

Scenario C# e VB (CLR) C++/WinRT C++/CX
Vincular a um objeto. Pode ser qualquer objeto. Pode ser qualquer objeto. O objeto deve ter BindableAttribute ou implementar ICustomPropertyProvider.
Obter notificações de alteração de propriedade de um objeto acoplado. O objeto deve implementar INotifyPropertyChanged. O objeto deve implementar INotifyPropertyChanged. O objeto deve implementar INotifyPropertyChanged.
Vincular a uma coleção. Lista (de T) IVector de IInspectable, ou IBindableObservableVector. Consulte Controles de itens XAML; vincular a uma coleção C++/WinRT e Coleções com C++/WinRT. Vetor<T>
Obter notificações de alteração de coleção de uma coleção acoplada. ObservableCollection(De T) IObservableVector de IInspectable. Por exemplo, winrt::single_threaded_observable_vetor<T>. IObservableVector<Não>. Vetor<T> implementa esta interface.
Implemente uma coleção que ofereça suporte à vinculação. Estenda List(Of T) ou implemente IList, IList(Of Object), IEnumerable ou IEnumerable(Of Object). Não há suporte para vinculação a IList(Of T) e IEnumerable(Of T) genéricos. Implementar IVector de IInspectable. Consulte Controles de itens XAML; vincular a uma coleção C++/WinRT e Coleções com C++/WinRT. Implemente IBindableVector, IBindableIterable, IVector<Object^>, IIterable<Object^>, IVector<IInspectable*> ou IIterable<IInspectable*>. Não há suporte para vinculação a IVector<T> genérico e IIterable<T> .
Implemente uma coleção que ofereça suporte a notificações de alteração de coleção. Estenda ObservableCollection(Of T) ou implemente (não genérico) IList e INotifyCollectionChanged. Implemente IObservableVector de IInspectable, ou IBindableObservableVector. Implemente IBindableVector e IBindableObservableVector.
Implemente uma coleção que ofereça suporte ao carregamento incremental. Estenda ObservableCollection(Of T) ou implemente (não genérico) IList e INotifyCollectionChanged. Além disso, implemente ISupportIncrementalLoading. Implemente IObservableVector de IInspectable, ou IBindableObservableVector. Além disso, implemente ISupportIncrementalLoading Implemente IBindableVector, IBindableObservableVector e ISupportIncrementalLoading.

Você pode vincular controles de lista a fontes de dados arbitrariamente grandes e ainda obter alto desempenho usando carregamento incremental. Por exemplo, você pode vincular controles de lista aos resultados da consulta de imagem do Bing sem ter que carregar todos os resultados de uma só vez. Em vez disso, você carrega apenas alguns resultados imediatamente e carrega resultados adicionais conforme necessário. Para dar suporte ao carregamento incremental, você deve implementar ISupportIncrementalLoading em uma fonte de dados que ofereça suporte a notificações de alteração de coleção. Quando o mecanismo de vinculação de dados solicita mais dados, sua fonte de dados deve fazer as solicitações apropriadas, integrar os resultados e, em seguida, enviar as notificações apropriadas para atualizar a interface do usuário.

Destino vinculativo

Nos dois exemplos abaixo, a propriedade Button.Content é o destino de vinculação e seu valor é definido como uma extensão de marcação que declara o objeto de ligação. Primeiro {x:Bind} é mostrado e, em seguida, {Binding}. Declarar ligações na marcação é o caso comum (é conveniente, legível e ferramental). Mas você pode evitar a marcação e criar imperativamente (programaticamente) uma instância da classe Binding em vez disso, se necessário.

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

Se você estiver usando extensões de componente C++/WinRT ou Visual C++ (C++/CX), precisará adicionar o atributo BindableAttribute a qualquer classe de tempo de execução com a qual deseja usar a extensão de marcação {Binding} .

Importante

Se você estiver usando C++/WinRT, o atributo BindableAttribute estará disponível se você tiver instalado o SDK do Windows versão 10.0.17763.0 (Windows 10, versão 1809) ou posterior. Sem esse atributo, você precisará implementar as interfaces ICustomPropertyProvider e ICustomProperty para poder usar a extensão de marcação {Binding} .

Objeto de vinculação declarado usando {x:Bind}

Há uma etapa que precisamos fazer antes de criarmos nossa marcação {x:Bind} . Precisamos expor nossa classe de origem de vinculação da classe que representa nossa página de marcação. Fazemos isso adicionando uma propriedade (do tipo HostViewModel neste caso) à nossa classe de página MainPage .

namespace DataBindingInDepth
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}
// MainPage.idl
import "HostViewModel.idl";

namespace DataBindingInDepth
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        HostViewModel ViewModel{ get; };
    }
}

// MainPage.h
// Include a header, and add this field:
...
#include "HostViewModel.h"
...
    DataBindingInDepth::HostViewModel ViewModel();

private:
    DataBindingInDepth::HostViewModel m_viewModel{ nullptr };
...

// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
    InitializeComponent();

}

DataBindingInDepth::HostViewModel MainPage::ViewModel()
{
    return m_viewModel;
}
...

Feito isso, agora podemos dar uma olhada mais de perto na marcação que declara o objeto de vinculação. O exemplo abaixo usa o mesmo destino de vinculação Button.Content que usamos na seção "Destino de vinculação" anteriormente e mostra que ele está vinculado à propriedade HostViewModel.NextButtonText .

<!-- MainPage.xaml -->
<Page x:Class="DataBindingInDepth.Mainpage" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Page>

Observe o valor que especificamos para Path. Esse valor é interpretado no contexto da própria página e, nesse caso, o caminho começa fazendo referência à propriedade ViewModel que acabamos de adicionar à página MainPage . Essa propriedade retorna uma instância de HostViewModel e, portanto, podemos pontilhar nesse objeto para acessar a propriedade HostViewModel.NextButtonText . E especificamos Modo, para substituir o padrão {x:Bind} de uma vez.

A propriedade Path oferece suporte a uma variedade de opções de sintaxe para vinculação a propriedades aninhadas, propriedades anexadas e indexadores inteiros e de cadeia de caracteres. Para saber mais, veja Sintaxe de caminho de propriedade. A associação a indexadores de cadeia de caracteres oferece o efeito de vincular a propriedades dinâmicas sem ter que implementar ICustomPropertyProvider. Para outras configurações, consulte {x:Bind} markup extension.

Para ilustrar que a propriedade HostViewModel.NextButtonText é realmente observável, adicione um manipulador de eventos Click ao botão e atualize o valor de HostViewModel.NextButtonText. Crie, execute e clique no botão para ver o valor da atualização de conteúdo do botão.

// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.ViewModel.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ViewModel().NextButtonText(L"Updated Next button text");
}

Observação

As alterações em TextBox.Text são enviadas para uma fonte vinculada bidirecional quando a TextBox perde o foco, e não após cada pressionamento de tecla do usuário.

DataTemplate e x:DataType

Dentro de um DataTemplate (seja usado como um modelo de item, um modelo de conteúdo ou um modelo de cabeçalho), o valor de Path não é interpretado no contexto da página, mas no contexto do objeto de dados que está sendo modelado. Ao usar {x:Bind} em um modelo de dados, para que suas associações possam ser validadas (e código eficiente gerado para elas) em tempo de compilação, o DataTemplate precisa declarar o tipo de seu objeto de dados usando x:DataType. O exemplo dado abaixo pode ser usado como o ItemTemplate de um controle de itens vinculado a uma coleção de objetos SampleDataGroup .

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

Objetos digitados fracamente em seu caminho

Considere, por exemplo, que você tem um tipo chamado SampleDataGroup, que implementa uma propriedade string chamada Title. E você tem uma propriedade MainPage.SampleDataGroupAsObject, que é do tipo objeto, mas que na verdade retorna uma instância de SampleDataGroup. A associação <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> resultará em um erro de compilação porque a propriedade Title não é encontrada no objeto type. A solução para isso é adicionar um cast à sintaxe do Path como este: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Aqui está outro exemplo em que Element é declarado como objeto, mas na verdade é um TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>. E um elenco resolve a questão: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.

Se os dados forem carregados de forma assíncrona

O código para suportar {x:Bind} é gerado em tempo de compilação nas classes parciais das suas páginas. Esses arquivos podem ser encontrados em sua obj pasta, com nomes como (para C#) <view name>.g.cs. O código gerado inclui um manipulador para o evento Loading da sua página, e esse manipulador chama o método Initialize em uma classe gerada que representa as associações da sua página. Inicializar , por sua vez, chama Update para começar a mover dados entre a origem da vinculação e o destino. O carregamento é levantado imediatamente antes da primeira medida passar da página ou do controle do usuário. Portanto, se seus dados forem carregados de forma assíncrona, eles podem não estar prontos no momento em que Initialize for chamado. Assim, depois de carregar dados, você pode forçar ligações únicas a serem inicializadas chamando this.Bindings.Update();. Se você só precisa de ligações únicas para dados carregados de forma assíncrona, é muito mais barato inicializá-las dessa maneira do que ter ligações unidirecionais e ouvir as alterações. Se seus dados não sofrerem alterações refinadas e se for provável que sejam atualizados como parte de uma ação específica, você poderá fazer suas associações uma única vez e forçar uma atualização manual a qualquer momento com uma chamada para Atualizar.

Observação

{x:Bind} não é adequado para cenários de ligação tardia, como navegar na estrutura do dicionário de um objeto JSON, nem digitar pato. "Digitação de pato" é uma forma fraca de digitação baseada em correspondências lexicais em nomes de propriedades (como em, "se anda, nada e charlatanismo como um pato, então é um pato"). Com a digitação de pato, uma ligação à propriedade Age seria igualmente satisfeita com uma Pessoa ou um objeto Wine (assumindo que cada tipo tinha uma propriedade Age ). Para esses cenários, use a extensão de marcação {Binding} .

Objeto de vinculação declarado usando {Binding}

Se você estiver usando extensões de componente C++/WinRT ou Visual C++ (C++/CX), então, para usar a extensão de marcação {Binding} , você precisará adicionar o atributo BindableAttribute a qualquer classe de tempo de execução à qual deseja vincular. Para usar {x:Bind}, você não precisa desse atributo.

// HostViewModel.idl
// Add this attribute:
[Windows.UI.Xaml.Data.Bindable]
runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
...

Importante

Se você estiver usando C++/WinRT, o atributo BindableAttribute estará disponível se você tiver instalado o SDK do Windows versão 10.0.17763.0 (Windows 10, versão 1809) ou posterior. Sem esse atributo, você precisará implementar as interfaces ICustomPropertyProvider e ICustomProperty para poder usar a extensão de marcação {Binding} .

{Binding} assume, por padrão, que você está vinculando ao DataContext da sua página de marcação. Portanto, definiremos o DataContext de nossa página como uma instância de nossa classe de origem de vinculação (do tipo HostViewModel neste caso). O exemplo abaixo mostra a marcação que declara o objeto de ligação. Usamos o mesmo destino de vinculação Button.Content que usamos na seção "Destino de vinculação" anteriormente e vinculamos à propriedade HostViewModel.NextButtonText .

<Page xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Page.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Page.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Page>
// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.viewModelInDataContext.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    viewModelInDataContext().NextButtonText(L"Updated Next button text");
}

Observe o valor que especificamos para Path. Esse valor é interpretado no contexto do DataContext da página, que neste exemplo é definido como uma instância de HostViewModel. O caminho faz referência à propriedade HostViewModel.NextButtonText . Podemos omitir o Modo, porque o padrão {Binding} de unidirecional funciona aqui.

O valor padrão de DataContext para um elemento da interface do usuário é o valor herdado de seu pai. É claro que você pode substituir esse padrão definindo DataContext explicitamente, que por sua vez é herdado por filhos por padrão. Definir DataContext explicitamente em um elemento é útil quando você deseja ter várias associações que usam a mesma fonte.

Um objeto de associação tem uma propriedade Source , que assume como padrão o DataContext do elemento da interface do usuário no qual a associação é declarada. Você pode substituir esse padrão definindo Source, RelativeSource ou ElementName explicitamente na associação (consulte {Binding} para obter detalhes).

Dentro de um DataTemplate, o DataContext é definido automaticamente para o objeto de dados que está sendo modelado. O exemplo dado abaixo pode ser usado como o ItemTemplate de um controle de itens vinculado a uma coleção de qualquer tipo que tenha propriedades de cadeia de caracteres chamadas Title e Description.

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

Observação

Por padrão, as alterações em TextBox.Text são enviadas para uma fonte vinculada bidirecional quando a TextBox perde o foco. Para fazer com que as alterações sejam enviadas após cada pressionamento de tecla do usuário, defina UpdateSourceTrigger como PropertyChanged na associação na marcação. Você também pode assumir completamente o controle de quando as alterações são enviadas para a origem definindo UpdateSourceTrigger como Explicit. Em seguida, você manipula eventos na caixa de texto (normalmente TextBox.TextChanged), chama GetBindingExpression no destino para obter um objeto BindingExpression e, finalmente, chama BindingExpression.UpdateSource para atualizar programaticamente a fonte de dados.

A propriedade Path oferece suporte a uma variedade de opções de sintaxe para vinculação a propriedades aninhadas, propriedades anexadas e indexadores inteiros e de cadeia de caracteres. Para saber mais, veja Sintaxe de caminho de propriedade. A associação a indexadores de cadeia de caracteres oferece o efeito de vincular a propriedades dinâmicas sem ter que implementar ICustomPropertyProvider. A propriedade ElementName é útil para a vinculação de elemento a elemento. A propriedade RelativeSource tem vários usos, um dos quais é como uma alternativa mais poderosa para a vinculação de modelo dentro de um ControlTemplate. Para outras configurações, consulte {Binding} markup extension e a classe Binding .

E se a origem e o destino não forem do mesmo tipo?

Se você quiser controlar a visibilidade de um elemento da interface do usuário com base no valor de uma propriedade booleana, ou se quiser renderizar um elemento da interface do usuário com uma cor que seja uma função do intervalo ou tendência de um valor numérico, ou se quiser exibir um valor de data e/ou hora em uma propriedade de elemento da interface do usuário que espera uma cadeia de caracteres, Em seguida, você precisará converter valores de um tipo para outro. Haverá casos em que a solução certa é expor outra propriedade do tipo certo de sua classe de origem de vinculação e manter a lógica de conversão encapsulada e testável lá. Mas isso não é flexível nem escalável quando você tem grandes números, ou grandes combinações, de propriedades de origem e destino. Nesse caso, você tem algumas opções:

  • Se estiver usando {x:Bind}, você pode vincular diretamente a uma função para fazer essa conversão
  • Ou você pode especificar um conversor de valor que é um objeto projetado para executar a conversão

Conversores de valor

Aqui está um conversor de valor, adequado para uma ligação única ou unidirecional, que converte um valor DateTime em um valor de cadeia de caracteres que contém o mês. A classe implementa IValueConverter.

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisdate = (DateTime)value;
        int monthnum = thisdate.Month;
        string month;
        switch (monthnum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
// See the "Formatting or converting data values for display" section in the "Data binding overview" topic.

E aqui está como você consome esse conversor de valor em sua marcação de objeto de vinculação.

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

O mecanismo de vinculação chama os métodos Convert e ConvertBack se o parâmetro Converter for definido para a ligação. Quando os dados são passados da origem, o mecanismo de vinculação chama Convert e passa os dados retornados para o destino. Quando os dados são passados do destino (para uma ligação bidirecional), o mecanismo de vinculação chama ConvertBack e passa os dados retornados para a origem.

O conversor também tem parâmetros opcionais: ConverterLanguage, que permite especificar o idioma a ser usado na conversão, e ConverterParameter, que permite passar um parâmetro para a lógica de conversão. Para obter um exemplo que usa um parâmetro conversor, consulte IValueConverter.

Observação

Se houver um erro na conversão, não lance uma exceção. Em vez disso, retorne DependencyProperty.UnsetValue, que interromperá a transferência de dados.

Para exibir um valor padrão a ser usado sempre que a origem da vinculação não puder ser resolvida, defina a propriedade FallbackValue no objeto de vinculação na marcação. Isso é útil para lidar com erros de conversão e formatação. Também é útil vincular a propriedades de origem que podem não existir em todos os objetos em uma coleção acoplada de tipos heterogêneos.

Se você vincular um controle de texto a um valor que não seja uma cadeia de caracteres, o mecanismo de vinculação de dados converterá o valor em uma cadeia de caracteres. Se o valor for um tipo de referência, o mecanismo de vinculação de dados recuperará o valor da cadeia de caracteres chamando ICustomPropertyProvider.GetStringRepresentation ou IStringable.ToString , se disponível, e chamará Object.ToString. Observe, no entanto, que o mecanismo de vinculação ignorará qualquer implementação ToString que oculte a implementação de classe base. Em vez disso, as implementações de subclasse devem substituir o método ToString da classe base. Da mesma forma, em idiomas nativos, todos os objetos gerenciados parecem implementar ICustomPropertyProvider e IStringable. No entanto, todas as chamadas para GetStringRepresentation e IStringable.ToString são roteadas para Object.ToString ou uma substituição desse método, e nunca para uma nova implementação ToString que oculta a implementação de classe base.

Observação

A partir do Windows 10, versão 1607, a estrutura XAML fornece um conversor booleano para visibilidade integrado. O conversor mapeia true para o valor de enumeração Visible e false para Collapsed para que você possa vincular uma propriedade Visibility a um booleano sem criar um conversor. Para usar o conversor interno, a versão mínima do SDK de destino do seu aplicativo deve ser 14393 ou posterior. Você não pode usá-lo quando seu aplicativo tem como alvo versões anteriores do Windows 10. Para obter mais informações sobre versões de destino, consulte Código adaptável de versão.

Vinculação de função em {x:Bind}

{x:Bind} permite que a etapa final em um caminho de vinculação seja uma função. Isso pode ser usado para executar conversões e para executar associações que dependem de mais de uma propriedade. Consulte Funções em x:Bind

Vinculação de elemento a elemento

Você pode vincular a propriedade de um elemento XAML à propriedade de outro elemento XAML. Aqui está um exemplo de como isso parece na marcação.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Importante

Para obter o fluxo de trabalho necessário para a vinculação de elemento a elemento usando C++/WinRT, consulte Vinculação de elemento a elemento.

Dicionários de recursos com {x:Bind}

A extensão de marcação {x:Bind} depende da geração de código, por isso precisa de um arquivo code-behind contendo um construtor que chama InitializeComponent (para inicializar o código gerado). Você reutiliza o dicionário de recursos instanciando seu tipo (para que InitializeComponent seja chamado) em vez de referenciar seu nome de arquivo. Aqui está um exemplo do que fazer se você tiver um dicionário de recursos existente e quiser usar {x:Bind} nele.

TemplatesResourceDictionary.xaml

<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.cs

using Windows.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}

MainPage.xaml

<Page x:Class="ExampleNamespace.MainPage"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Page.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>
</Page>

Misturando {x:Bind} e {Binding} em um estilo reutilizável

Embora o exemplo anterior tenha mostrado o uso de {x:Bind} em DataTemplates, você também pode criar estilos reutilizáveis que combinam as extensões de marcação {x:Bind} e {Binding}. Isso é útil quando você deseja vincular algumas propriedades a valores conhecidos em tempo de compilação usando {x:Bind} e outras propriedades a valores DataContext de tempo de execução usando {Binding}.

Veja um exemplo que mostra como criar um estilo Button reutilizável que usa ambas as abordagens de vinculação:

TemplatesResourceDictionary.xaml

<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <!-- DataTemplate using x:Bind -->
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
    
    <!-- Style that mixes x:Bind and Binding -->
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="{Binding ButtonBackgroundBrush}"/>
        <Setter Property="Foreground" Value="{Binding ButtonForegroundBrush}"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="Margin" Value="4"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border x:Name="RootBorder"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="4">
                        <StackPanel Orientation="Horizontal" 
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center">
                            <!-- x:Bind to a static property or page-level property -->
                            <Ellipse Width="8" Height="8" 
                                     Fill="{x:Bind DefaultIndicatorBrush}" 
                                     Margin="0,0,8,0"/>
                            <!-- Binding to DataContext -->
                            <ContentPresenter x:Name="ContentPresenter"
                                              Content="{TemplateBinding Content}"
                                              Foreground="{TemplateBinding Foreground}"
                                              FontSize="{TemplateBinding FontSize}"/>
                        </StackPanel>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="PointerOver">
                                    <VisualState.Setters>
                                        <!-- Binding to DataContext for hover color -->
                                        <Setter Target="RootBorder.Background" 
                                                Value="{Binding ButtonHoverBrush}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <VisualState.Setters>
                                        <!-- x:Bind to a compile-time known resource -->
                                        <Setter Target="RootBorder.Background" 
                                                Value="{x:Bind DefaultPressedBrush}"/>
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.cs

using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
        
        // Properties for x:Bind - these are compile-time bound
        public SolidColorBrush DefaultIndicatorBrush { get; } = 
            new SolidColorBrush(Colors.Green);
            
        public SolidColorBrush DefaultPressedBrush { get; } = 
            new SolidColorBrush(Colors.DarkGray);
    }
}

Uso em MainPage.xaml com um ViewModel que fornece valores de tempo de execução:

<Page x:Class="ExampleNamespace.MainPage"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>
    
    <Page.DataContext>
        <examplenamespace:ButtonThemeViewModel/>
    </Page.DataContext>

    <StackPanel Margin="20">
        <!-- This button uses the mixed binding style -->
        <Button Content="Save" Style="{StaticResource CustomButtonStyle}"/>
        <Button Content="Cancel" Style="{StaticResource CustomButtonStyle}"/>
    </StackPanel>
</Page>

ButtonThemeViewModel.cs (o DataContext que fornece valores de vinculação de tempo de execução):

using System.ComponentModel;
using Windows.UI;
using Windows.UI.Xaml.Media;

namespace ExampleNamespace
{
    public class ButtonThemeViewModel : INotifyPropertyChanged
    {
        private SolidColorBrush _buttonBackgroundBrush = new SolidColorBrush(Colors.LightBlue);
        private SolidColorBrush _buttonForegroundBrush = new SolidColorBrush(Colors.DarkBlue);
        private SolidColorBrush _buttonHoverBrush = new SolidColorBrush(Colors.LightCyan);

        public SolidColorBrush ButtonBackgroundBrush
        {
            get => _buttonBackgroundBrush;
            set
            {
                _buttonBackgroundBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonBackgroundBrush)));
            }
        }

        public SolidColorBrush ButtonForegroundBrush
        {
            get => _buttonForegroundBrush;
            set
            {
                _buttonForegroundBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonForegroundBrush)));
            }
        }

        public SolidColorBrush ButtonHoverBrush
        {
            get => _buttonHoverBrush;
            set
            {
                _buttonHoverBrush = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonHoverBrush)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

Neste exemplo:

  • {Binding} é usado para propriedades que dependem do DataContext (ButtonBackgroundBrush, ButtonForegroundBrush, ButtonHoverBrush)
  • {x:Bind} é usado para propriedades que são conhecidas em tempo de compilação e pertencem ao próprio ResourceDictionary (DefaultIndicatorBrush, DefaultPressedBrush)
  • O estilo é reutilizável e pode ser aplicado a qualquer botão
  • A temática de tempo de execução é possível através do DataContext enquanto ainda se beneficia do desempenho de {x:Bind} para elementos estáticos

Vinculação de eventos e ICommand

{x:Bind} suporta um recurso chamado vinculação de eventos. Com esse recurso, você pode especificar o manipulador para um evento usando uma ligação, que é uma opção adicional além de manipular eventos com um método no arquivo code-behind. Digamos que você tenha uma propriedade RootFrame em sua classe MainPage .

public sealed partial class MainPage : Page
{
    ...
    public Frame RootFrame { get { return Window.Current.Content as Frame; } }
}

Em seguida, você pode vincular o evento Click de um botão a um método no objeto Frame retornado pela propriedade RootFrame como este. Observe que também vinculamos a propriedade IsEnabled do botão a outro membro do mesmo Frame.

<AppBarButton Icon="Forward" IsCompact="True"
IsEnabled="{x:Bind RootFrame.CanGoForward, Mode=OneWay}"
Click="{x:Bind RootFrame.GoForward}"/>

Métodos sobrecarregados não podem ser usados para manipular um evento com essa técnica. Além disso, se o método que manipula o evento tiver parâmetros, todos eles devem ser atribuíveis a partir dos tipos de todos os parâmetros do evento, respectivamente. Neste caso, Frame.GoForward não está sobrecarregado e não tem parâmetros (mas ainda seria válido mesmo se tivesse dois parâmetros de objeto ). Frame.GoBack está sobrecarregado, portanto, não podemos usar esse método com essa técnica.

A técnica de vinculação de eventos é semelhante à implementação e consumo de comandos (um comando é uma propriedade que retorna um objeto que implementa a interface ICommand ). { x:Bind} e {Binding} funcionam com comandos. Para que você não precise implementar o padrão de comando várias vezes, você pode usar a classe auxiliar DelegateCommand que você encontrará no exemplo QuizGame (na pasta "Common").

Vinculação a uma coleção de pastas ou arquivos

Você pode usar as APIs no namespace Windows.Storage para recuperar dados de pastas e arquivos. No entanto, os vários GetFilesAsync, GetFoldersAsync e GetItemsAsync métodos não retornam valores que são adequados para ligação a controles de lista. Em vez disso, você deve vincular aos valores de retorno dos métodos GetVirtualizedFilesVector, GetVirtualizedFoldersVector e GetVirtualizedItemsVector da classe FileInformationFactory . O exemplo de código a seguir do exemplo StorageDataSource e GetVirtualizedFilesVector mostra o padrão de uso típico. Lembre-se de declarar o recurso picturesLibrary no manifesto do pacote do aplicativo e confirme se há imagens na pasta Biblioteca de imagens.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

Normalmente, você usará essa abordagem para criar uma exibição somente leitura das informações de arquivos e pastas. Você pode criar ligações bidirecionais para as propriedades de arquivo e pasta, por exemplo, para permitir que os usuários classifiquem uma música em uma exibição de música. No entanto, as alterações não são mantidas até que você chame o método SavePropertiesAsync apropriado (por exemplo, MusicProperties.SavePropertiesAsync). Você deve confirmar alterações quando o item perder o foco, pois isso aciona uma redefinição de seleção.

Observe que a vinculação bidirecional usando essa técnica funciona apenas com locais indexados, como Música. Você pode determinar se um local está indexado chamando o método FolderInformation.GetIndexedStateAsync .

Observe também que um vetor virtualizado pode retornar null para alguns itens antes de preencher seu valor. Por exemplo, você deve verificar se há null antes de usar o valor SelectedItem de um controle de lista vinculado a um vetor virtualizado ou usar SelectedIndex em vez disso.

Vinculação a dados agrupados por uma chave

Se você pegar uma coleção simples de itens (livros, por exemplo, representados por uma classe BookSku ) e agrupar os itens usando uma propriedade comum como chave (a propriedade BookSku.AuthorName , por exemplo), o resultado será chamado de dados agrupados. Quando você agrupa dados, eles não são mais uma coleção simples. Dados agrupados são uma coleção de objetos de grupo, onde cada objeto de grupo tem

  • uma chave, e
  • Uma coleção de itens cuja propriedade corresponde a essa chave.

Para tomar o exemplo de livros novamente, o resultado do agrupamento dos livros por nome de autor resulta em uma coleção de grupos de nomes de autores onde cada grupo tem

  • uma chave, que é um nome de autor, e
  • uma coleção de BookSkus cuja propriedade AuthorName corresponde à chave do grupo.

Em geral, para exibir uma coleção, você vincula o ItemsSource de um controle de itens (como ListView ou GridView) diretamente a uma propriedade que retorna uma coleção. Se essa é uma coleção simples de itens, então você não precisa fazer nada de especial. Mas se for uma coleção de objetos de grupo (como é quando se vincula a dados agrupados), então você precisa dos serviços de um objeto intermediário chamado CollectionViewSource , que fica entre o controle de itens e a fonte de vinculação. Você vincula o CollectionViewSource à propriedade que retorna dados agrupados e vincula o controle items ao CollectionViewSource. Um valor agregado extra de um CollectionViewSource é que ele controla o item atual, para que você possa manter mais de um controle de itens sincronizado vinculando-os todos ao mesmo CollectionViewSource. Você também pode acessar o item atual programaticamente por meio da propriedade ICollectionView.CurrentItem do objeto retornado pela propriedade CollectionViewSource.View .

Para ativar o recurso de agrupamento de um CollectionViewSource, defina IsSourceGrouped como true. Se você também precisa definir a propriedade ItemsPath depende exatamente de como você cria seus objetos de grupo. Há duas maneiras de criar um objeto de grupo: o padrão "is-a-group" e o padrão "has-a-group". No padrão "is-a-group", o objeto group deriva de um tipo de coleção (por exemplo, List<T>), portanto, o objeto group na verdade é o próprio grupo de itens. Com esse padrão, você não precisa definir ItemsPath. No padrão "has-a-group", o objeto group tem uma ou mais propriedades de um tipo de coleção (como List<T>), portanto, o grupo "tem um" grupo de itens na forma de uma propriedade (ou vários grupos de itens na forma de várias propriedades). Com esse padrão, você precisa definir ItemsPath como o nome da propriedade que contém o grupo de itens.

O exemplo abaixo ilustra o padrão "tem um grupo". A classe page tem uma propriedade chamada ViewModel, que retorna uma instância de nosso modelo de exibição. O CollectionViewSource se liga à propriedade Authors do modelo de exibição (Authors é a coleção de objetos de grupo) e também especifica que é a propriedade Author.BookSkus que contém os itens agrupados. Finalmente, o GridView está vinculado ao CollectionViewSource e tem seu estilo de grupo definido para que ele possa renderizar os itens em grupos.

<Page.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Page.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

Você pode implementar o padrão "is-a-group" de duas maneiras. Uma maneira é criar sua própria aula em grupo. Derive a classe da Lista<T> (onde T é o tipo dos itens). Por exemplo, public class Author : List<BookSku>. A segunda maneira é usar uma expressão LINQ para criar dinamicamente objetos de grupo (e uma classe de grupo) a partir de valores de propriedade semelhantes dos itens BookSku . Essa abordagem, mantendo apenas uma lista simples de itens e agrupando-os em tempo real, é típica de um aplicativo que acessa dados de um serviço de nuvem. Você tem a flexibilidade de agrupar livros por autor ou por gênero (por exemplo) sem precisar de aulas de grupo especiais, como Autor e Gênero.

O exemplo abaixo ilustra o padrão "is-a-group" usando LINQ. Desta vez, agrupamos os livros por gênero, exibidos com o nome do gênero nos cabeçalhos do grupo. Isso é indicado pelo caminho da propriedade "Key" em referência ao valor da chave do grupo.

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (this.genres == null)
        {
            this.genres = from book in this.bookSkus
                          group book by book.genre into grp
                          orderby grp.Key
                          select grp;
        }
        return this.genres;
    }
}

Lembre-se de que, ao usar {x:Bind} com modelos de dados, precisamos indicar o tipo ao qual estamos vinculados, definindo um valor x:DataType . Se o tipo for genérico, não podemos expressar isso na marcação, então precisamos usar {Binding} no modelo de cabeçalho de estilo de grupo.

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

Um controle SemanticZoom é uma ótima maneira de seus usuários visualizarem e navegarem por dados agrupados. O aplicativo de exemplo Bookstore2 ilustra como usar o SemanticZoom. Nessa aplicação, pode ver uma lista de livros agrupados por autor (a vista ampliada) ou pode reduzir para ver uma lista de atalhos de autores (a vista reduzida). A lista de atalhos permite uma navegação muito mais rápida do que percorrer a lista de livros. As exibições ampliadas e reduzidas são, na verdade , controles ListView ou GridView vinculados ao mesmo CollectionViewSource.

Uma ilustração de um SemanticZoom

Ao vincular a dados hierárquicos, como subcategorias dentro de categorias, você pode optar por exibir os níveis hierárquicos em sua interface do usuário com uma série de controles de itens. Uma seleção em um controle de itens determina o conteúdo dos controles de itens subsequentes. Você pode manter as listas sincronizadas vinculando cada lista a seu próprio CollectionViewSource e vinculando as instâncias CollectionViewSource juntas em uma cadeia. Isso é chamado de exibição mestre/detalhes (ou lista/detalhes). Para saber mais, veja Como vincular a dados hierárquicos e criar uma exibição mestre/detalhes.

Diagnosticar e depurar problemas de vinculação de dados

Sua marcação de vinculação contém os nomes das propriedades (e, para C#, às vezes campos e métodos). Portanto, ao renomear uma propriedade, você também precisará alterar qualquer associação que faça referência a ela. Esquecer de fazer isso leva a um exemplo típico de um bug de vinculação de dados, e seu aplicativo não será compilado ou não será executado corretamente.

Os objetos de ligação criados por {x:Bind} e {Binding} são amplamente equivalentes funcionalmente. Mas {x:Bind} tem informações de tipo para a fonte de vinculação e gera código-fonte em tempo de compilação. Com {x:Bind} você obtém o mesmo tipo de deteção de problemas que obtém com o resto do seu código. Isso inclui a validação em tempo de compilação de suas expressões de vinculação e depuração definindo pontos de interrupção no código-fonte gerado como a classe parcial para sua página. Essas classes podem ser encontradas nos arquivos em sua obj pasta, com nomes como (para C#) <view name>.g.cs). Se você tiver um problema com uma ligação, ative Interromper exceções não tratadas no depurador do Microsoft Visual Studio. O depurador interromperá a execução nesse ponto, e você poderá depurar o que deu errado. O código gerado por {x:Bind} segue o mesmo padrão para cada parte do gráfico de nós de origem de vinculação, e você pode usar as informações na janela Pilha de chamadas para ajudar a determinar a sequência de chamadas que levaram ao problema.

{Binding} não tem informações de tipo para a fonte de ligação. Mas quando você executa seu aplicativo com o depurador anexado, todos os erros de associação aparecem na janela Saída no Visual Studio.

Criando ligações no código

Observação Esta seção só se aplica a {Binding}, porque você não pode criar { x:Bind} bindings no código. No entanto, alguns dos mesmos benefícios de {x:Bind} podem ser alcançados com DependencyObject.RegisterPropertyChangedCallback, que permite que você se registre para notificações de alteração em qualquer propriedade de dependência.

Você também pode conectar elementos da interface do usuário a dados usando código de procedimento em vez de XAML. Para fazer isso, crie um novo objeto Binding , defina as propriedades apropriadas e chame FrameworkElement.SetBinding ou BindingOperations.SetBinding. Criar associações programaticamente é útil quando você deseja escolher os valores de propriedade de vinculação em tempo de execução ou compartilhar uma única associação entre vários controles. Observe, no entanto, que você não pode alterar os valores de propriedade de vinculação depois de chamar SetBinding.

O exemplo a seguir mostra como implementar uma associação no código.

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
MyColors textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
Binding binding = new Binding() { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);
' Create an instance of the MyColors class 
' that implements INotifyPropertyChanged. 
Dim textcolor As New MyColors()

' Brush1 is set to be a SolidColorBrush with the value Red. 
textcolor.Brush1 = New SolidColorBrush(Colors.Red)

' Set the DataContext of the TextBox MyTextBox. 
MyTextBox.DataContext = textcolor

' Create the binding and associate it with the text box.
Dim binding As New Binding() With {.Path = New PropertyPath("Brush1")}
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding)

Comparação de recursos {x:Bind} e {Binding}

Característica {x:Bind} vs. {Binding} Observações
Path é a propriedade padrão {x:Bind a.b.c}
-
{Binding a.b.c}
Propriedade Path {x:Bind Path=a.b.c}
-
{Binding Path=a.b.c}
Em x:Bind, Path está enraizado na Page por padrão, não no DataContext.
Indexer {x:Bind Groups[2].Title}
-
{Binding Groups[2].Title}
Liga-se ao item especificado na coleção. Apenas índices baseados em números inteiros são suportados.
Propriedades anexadas {x:Bind Button22.(Grid.Row)}
-
{Binding Button22.(Grid.Row)}
As propriedades anexadas são especificadas entre parênteses. Se a propriedade não for declarada em um namespace XAML, prefixe-a com um namespace xml, que deve ser mapeado para um namespace de código no cabeçalho do documento.
Fundição {x:Bind groups[0].(data:SampleDataGroup.Title)}
-
Não é necessário para {Binding}.
Os moldes são especificados entre parênteses. Se a propriedade não for declarada em um namespace XAML, prefixe-a com um namespace xml, que deve ser mapeado para um namespace de código no cabeçalho do documento.
Conversor {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}}
Os conversores devem ser declarados na raiz do Page/ResourceDictionary ou em App.xaml.
ConverterParameter, ConverterLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
Os conversores devem ser declarados na raiz do Page/ResourceDictionary ou em App.xaml.
TargetNullValue {x:Bind Name, TargetNullValue=0}
-
{Binding Name, TargetNullValue=0}
Usado quando a folha da expressão de ligação é nula. Use aspas simples para um valor de cadeia de caracteres.
FallbackValue {x:Bind Name, FallbackValue='empty'}
-
{Binding Name, FallbackValue='empty'}
Usado quando qualquer parte do caminho para a ligação (exceto para a folha) é nula.
ElementName {x:Bind slider1.Value}
-
{Binding Value, ElementName=slider1}
Com {x:Bind} você está vinculando a um campo; O caminho está enraizado na Página por padrão, portanto, qualquer elemento nomeado pode ser acessado por meio de seu campo.
RelativoFonte: Self <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />
-
<Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... />
Com {x:Bind}, nomeie o elemento e use seu nome em Path.
RelativeSource: TemplatedParent Não é necessário para {x:Bind}
-
{Binding <path>, RelativeSource={RelativeSource TemplatedParent}}
Com {x:Bind}, TargetType em ControlTemplate indica a vinculação ao modelo pai. Para {Binding} A vinculação de modelo regular pode ser usada em modelos de controle para a maioria dos usos. Mas use TemplatedParent onde você precisa usar um conversor ou uma ligação bidirecional.<
Fonte Não é necessário para {x:Bind}
-
<ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/>
Para {x:Bind} você pode usar diretamente o elemento nomeado, usar uma propriedade ou um caminho estático.
Mode {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
O modo pode ser OneTime, OneWay, ou TwoWay. {x:Bind} assume como padrão OneTime; {Binding} assume como padrão OneWay.
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger pode ser Default, LostFocus ou PropertyChanged. {x:Bind} não suporta UpdateSourceTrigger=Explicit. {x:Bind} usa o comportamento PropertyChanged para todos os casos, exceto TextBox.Text, onde usa o comportamento LostFocus.