controlos XAML; vincular a uma propriedade C++/WinRT

Uma propriedade que pode ser efetivamente ligada a um controlo XAML é conhecida como propriedade observável . Esta ideia baseia-se no padrão de design de software conhecido como padrão do observador. Este tópico mostra como implementar propriedades observáveis em C++/WinRT e como associar controlos XAML a elas (para informações de fundo, veja Data binding).

Important

Para conceitos e termos essenciais que apoiam a sua compreensão de como consumir e criar classes de runtime com C++/WinRT, veja Consume APIs with C++/WinRT e Author APIs with C++/WinRT.

O que significa observável para uma propriedade?

Imaginemos que uma classe de runtime chamada BookSku tem uma propriedade chamada Título. Se BookSku gerar o evento INotifyPropertyChanged::PropertyChanged sempre que o valor de Title se altera, isso significa que Title é uma propriedade observável. É o comportamento do BookSku (elevar ou não elevar o evento) que determina quais, se houver, das suas propriedades são observáveis.

Um elemento de texto XAML, ou controlo, pode associar-se e tratar destes eventos. Tal elemento ou controlo gere o evento recuperando o(s) valor(es) atualizado(s) e depois atualizando-se para mostrar o novo valor.

Note

Para informações sobre a instalação e utilização da Extensão Visual Studio C++/WinRT (VSIX) e do pacote NuGet (que juntos fornecem suporte para templates de projeto e compilação), consulte o suporte Visual Studio para C++/WinRT.

Criar uma Aplicação em Branco (Livraria)

Comece criando um novo projeto no Microsoft Visual Studio. Crie uma Aplicação em Branco, Empacotada (WinUI 3 no Ambiente de Trabalho) para um projeto C++, e dê-lhe o nome de Bookstore. Certifica-te de que Colocar solução e projeto no mesmo diretório está desmarcado. Utilize a versão mais recente do SDK do Windows disponível ao público (ou seja, não uma versão de pré-visualização).

Vamos criar uma nova classe para representar um livro que tem uma propriedade de título observável. Estamos a definir e a consumir a classe dentro da mesma unidade de compilação. Mas queremos poder fazer binding a esta classe a partir do XAML, e por isso vai ser uma classe de execução. E vamos usar C++/WinRT tanto para o desenvolver como para o consumir.

O primeiro passo na criação de uma nova classe de runtime é adicionar um novo item Midl File (.idl) ao projeto. Nomeie o novo item BookSku.idl. Apague o conteúdo padrão de BookSku.idl, e cole esta declaração de classe em tempo de execução.

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

Note

As suas classes de modelo de vista — na verdade, qualquer classe de runtime que declare na sua aplicação — não precisam de derivar de uma classe base. A classe BookSku mencionada acima é um exemplo disso. Implementa uma interface, mas não deriva de nenhuma classe base.

Qualquer classe de execução que declare na aplicação e que derive de uma classe base é conhecida como classe componível . E há restrições às 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. Isto significa que a classe na raiz da hierarquia de herança deve ser um tipo originário de um espaço de nomes Windows.* ou Microsoft.*. Se precisares de derivar uma classe de runtime a partir de uma classe base — por exemplo, para implementar uma classe BindableBase para todos os teus modelos de visualização — então podes derivar da Microsoft. UI. Xaml.DependencyObject.

Um modelo de vista é uma abstração de uma vista, e por isso está diretamente ligado à visualização (a marcação XAML). Um modelo de dados é uma abstração de dados, e é consumido apenas a partir dos teus modelos de visualização, não diretamente vinculado ao XAML. Assim, podes declarar os teus modelos de dados não como classes de tempo de execução, mas como estruturas ou classes em C++. Não precisam de ser declarados no MIDL, e és livre de usar a hierarquia de herança que quiseres.

Guarda o ficheiro e constrói o projeto. A build ainda não vai (totalmente) ter sucesso, mas vai fazer algumas coisas necessárias para nós. Especificamente, durante o processo de compilação, a midl.exe ferramenta é executada para criar um ficheiro de metadados do Windows Runtime que descreve a classe de tempo de execução (o ficheiro é colocado no disco em \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Depois, executa-se a ferramenta cppwinrt.exe para gerar ficheiros de código-fonte que ajudem a criar e utilizar a sua classe de runtime. Esses ficheiros incluem stubs para te ajudar a começar a implementar a classe de runtime BookSku que declaraste no teu IDL. Vamos encontrá-los no disco daqui a pouco, mas esses esboços são \Bookstore\Bookstore\Generated Files\sources\BookSku.h e BookSku.cpp.

Agora clique com o botão direito no nó do projeto no Visual Studio e clique em Abrir Pasta no Explorador de Ficheiros. Isso abre a pasta do projeto no Explorador de Ficheiros. Agora deves estar a olhar para o conteúdo da \Bookstore\Bookstore\ pasta. A partir daí, navega até à \Generated Files\sources\ pasta e copia os ficheiros BookSku.h stub e BookSku.cpp para a prancheta. Navega de volta até à pasta project (\Bookstore\Bookstore\) e cola os dois ficheiros que acabaste de copiar. Por fim, em Explorador de Soluções com o nó do projeto selecionado, certifique-se de que Mostrar Todos os Ficheiros está ativado. Clique com o botão direito nos ficheiros stub que copiou e clique em Incluir em Project.

Implementar BookSku

Agora vamos abrir \Bookstore\Bookstore\BookSku.h e BookSku.cpp implementar a nossa classe de runtime. Primeiro, verá static_assert na parte superior de BookSku.h e BookSku.cpp, que terá de remover.

De seguida, em BookSku.h, faça estas alterações.

  • No construtor padrão, altere = default para = delete. Isto porque não queremos um construtor padrão.
  • Adicione um membro privado para armazenar a cadeia de caracteres do título. Note que temos um construtor que assume um valor winrt::hstring . Esse valor é a string de título.
  • Adicione outro membro privado para o evento que dispararemos quando o título for alterado.

Depois de fazer estas alterações, o aspeto do seu BookSku.h ficará assim.

// BookSku.h
#pragma once
#include "BookSku.g.h"

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

Em BookSku.cpp, implemente as funções desta forma.

// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

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

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

Na função mutador Título , verificamos se está a ser definido um valor diferente do valor atual. E, se assim for, atualizamos o título e também disparamos o evento INotifyPropertyChanged::PropertyChanged com um argumento correspondente ao nome da propriedade que foi alterada. Isto serve para que a interface de utilizador (UI) saiba qual o valor da propriedade a consultar novamente.

O projeto vai ser construído novamente agora, se quiseres verificar isso.

Declarar e implementar o BookstoreViewModel

A nossa página principal de XAML vai ser associada a um modelo de vista principal. E esse modelo de visualização vai ter várias propriedades, incluindo uma do tipo BookSku. Neste passo, vamos declarar e implementar a nossa classe de runtime do modelo principal de visualização.

Adicione um novo item Midl File (.idl) chamado BookstoreViewModel.idl. Mas veja também Incluir classes de runtime em ficheiros Midl (.idl).

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

Guardar e compilar (a compilação ainda não será totalmente bem-sucedida, mas o motivo por que estamos a compilar é gerar novamente os ficheiros stub).

Copie BookstoreViewModel.h e BookstoreViewModel.cpp da pasta Generated Files\sources para a pasta do projeto e inclua-os no projeto. Abra esses ficheiros (removendo o static_assert novamente) e implemente a classe de runtime como mostrado abaixo. Note como, em BookstoreViewModel.h, estamos a incluir BookSku.h, que declara o tipo de implementação para o BookSku (que é winrt::Bookstore::implementation::BookSku). E estamos a remover = default do construtor predefinido.

Note

Nas listagens abaixo para BookstoreViewModel.h e BookstoreViewModel.cpp, o código ilustra a forma padrão de construir o m_bookSku membro de dados. Esse é o método que remonta à primeira versão do C++/WinRT, e é boa ideia estar pelo menos familiarizado com o padrão. Com C++/WinRT versão 2.0 e posteriores, existe uma forma otimizada de construção disponível conhecida como construção uniforme (ver Notícias e alterações em C++/WinRT 2.0). Mais adiante neste tópico, mostraremos um exemplo de construção uniforme.

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

Note

O tipo de m_bookSku é o tipo projetado (winrt::Bookstore::BookSku), e o parâmetro do modelo que usas com winrt::make é o tipo de implementação (winrt::Bookstore::implementation::BookSku). Ainda assim, make devolve uma instância do tipo projetado.

O projeto vai crescer novamente agora.

Adicionar uma propriedade do tipo BookstoreViewModel à Página Principal

Open MainPage.idl, que declara a classe de runtime que representa a nossa página principal da interface.

  • Adicionar uma import diretiva para importar BookstoreViewModel.idl.
  • Adicione uma propriedade de apenas leitura chamada MainViewModel, do tipo BookstoreViewModel.
  • Remove a propriedade MyProperty .
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Salve o arquivo. O projeto ainda não terá sucesso total na construção, mas construir agora é útil porque regenera os ficheiros de código-fonte onde a classe de runtime MainPage está implementada (\Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp). Por isso, avança e constrói agora. O erro de compilação que pode esperar ver nesta fase é 'MainViewModel': não é membro de 'winrt::Bookstore::implementation::MainPage'.

Se omitir a diretiva include de BookstoreViewModel.idl (ver a listagem de MainPage.idl acima), verá o erro à espera de < perto de "MainViewModel". Outra dica é garantir que deixa todos os tipos no mesmo espaço de nomes — o espaço de nomes mostrado nas listagens de código.

Para resolver o erro que esperamos ver, terá agora de copiar os stubs de acesso da propriedade MainViewModel dos ficheiros gerados (\Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp) para \Bookstore\Bookstore\MainPage.h e MainPage.cpp. Os passos para isso são descritos a seguir.

Em \Bookstore\Bookstore\MainPage.h, execute estes passos.

  • Inclui BookstoreViewModel.h, que declara o tipo de implementação para BookstoreViewModel (que é winrt::Bookstore::implementation::BookstoreViewModel).
  • Adicione um membro privado para armazenar o modelo de visualização. Repare que a função de acesso à propriedade (e o membro m_mainViewModel) está implementada em termos do tipo projetado para BookstoreViewModel (que é Bookstore::BookstoreViewModel).
  • O tipo de implementação está no mesmo projeto (unidade de compilação) que a aplicação, por isso construímos m_mainViewModel através da sobrecarga do construtor que toma std::nullptr_t.
  • Remove a propriedade MyProperty .

Note

No par de listagens abaixo para MainPage.h e MainPage.cpp, o código ilustra a forma padrão de construir o m_mainViewModel membro de dados. Na secção que se segue, mostraremos uma versão que utiliza a construção uniforme em vez disso.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

Em \Bookstore\Bookstore\MainPage.cpp, como mostrado na lista abaixo, faça as seguintes alterações.

  • Chame winrt::make (com o tipo de implementação BookstoreViewModel) para atribuir uma nova instância do tipo BookstoreViewModel projetado a m_mainViewModel. Como vimos acima, o construtor BookstoreViewModel cria um novo objeto BookSku como membro privado de dados, definindo inicialmente o seu título para L"Atticus".
  • No handler de eventos do botão (ClickHandler), atualize o título do livro para o título publicado.
  • Implemente o acessório para a propriedade MainViewModel .
  • Remove a propriedade MyProperty .
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Microsoft::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

Construção uniforme

Para usar a construção uniforme em vez de winrt::make, declare MainPage.h e inicializa m_mainViewModel em apenas um passo, como mostrado abaixo.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

E depois, no construtor MainPage em MainPage.cpp, não há necessidade do código m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Para mais informações sobre construção uniforme e exemplos de código, veja Optar para construção uniforme e acesso direto à implementação.

Associe o botão à propriedade Título

Abra o MainPage.xaml, que contém a marcação XAML para a nossa página principal da interface de utilizador. Como mostrado na listagem abaixo, remova o nome do botão e altere o valor da propriedade Conteúdo de literal para expressão vinculativa. Repare na propriedade Mode=OneWay na expressão de associação (unidirecional do modelo de visualização para a IU). Sem essa propriedade, a interface não responde a eventos de alteração de propriedade.

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

Agora, construa e execute o projeto. Clique no botão para executar o manipulador do evento Click. Esse processador invoca a função mutadora do título do livro; esse mutador desencadeia um evento para informar a IU de que a propriedade Title foi alterada; e o botão volta a consultar o valor dessa propriedade para atualizar o seu próprio valor de Content.

Usando a extensão de marcação {Binding} com C++/WinRT

Para a versão atualmente lançada de C++/WinRT, para poder usar a extensão de marcação {Binding} terá de implementar as interfaces ICustomPropertyProvider e ICustomProperty .

Vinculação de elemento a elemento

Você pode vincular a propriedade de um elemento XAML à propriedade de outro elemento XAML. Eis um exemplo de como isso fica em marcação.

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

Terá de declarar a entidade XAML nomeada myTextBox como uma propriedade só de leitura no seu ficheiro Midl (.idl).

// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
    MainPage();
    Microsoft.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

Aqui está a razão desta necessidade. Todos os tipos que o compilador XAML precisa de validar (incluindo os usados em {x:Bind}) são lidos a partir de Metadados do Windows (WinMD). Tudo o que precisa de fazer é adicionar a propriedade de apenas leitura ao seu ficheiro Midl. Não o implemente, porque o código subjacente XAML gerado automaticamente fornece-lhe a implementação.

Consumir objetos a partir da marcação XAML

Todas as entidades consumidas ao usar a extensão de marcação XAML {x:Bind} devem ser expostas publicamente no IDL. Além disso, se a marcação XAML contiver uma referência a outro elemento que também está na marcação, então o getter dessa marcação deve estar presente no IDL.

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

O elemento ChangeColorButton refere-se ao elemento UseCustomColorCheckBox através de binding. Assim, o IDL desta página tem de declarar uma propriedade de apenas leitura chamada UseCustomColorCheckBox para que seja acessível à vinculação.

O delegado de processamento do evento de clique para UseCustomColorCheckBox usa a sintaxe clássica de delegados de XAML, por isso não precisa de uma entrada no IDL; só precisa de ser público na sua classe de implementação. Por outro lado, ChangeColorButton também inclui um {x:Bind} processador do evento de clique, que também tem de ser incluído no IDL.

runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Microsoft.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

Não precisa de fornecer uma implementação para a propriedade UseCustomColorCheckBox . O gerador de código XAML faz isso por ti.

Vinculação a um valor booleano

Podes fazer isto num modo de diagnóstico:

<TextBlock Text="{Binding CanPair}"/>

Isso apresenta true ou false em C++/CX; mas apresenta Windows.Foundation.IReference`1<Boolean> em C++/WinRT.

Em vez disso, use x:Bind ao ligar a um booleano.

<TextBlock Text="{x:Bind CanPair}"/>

Utilização das Bibliotecas de Implementação do Windows (WIL)

As Bibliotecas de Implementação do Windows (WIL) fornecem ajudantes para facilitar a escrita de propriedades vinculáveis. Consulte Propriedades de Notificação na documentação WIL.

APIs importantes