Controles XAML; associar a uma propriedade de C++/WinRT

Uma propriedade que pode ser efetivamente vinculada a um controle de itens XAML é conhecida como uma propriedade observável. Essa ideia baseia-se no padrão de design de software conhecido como o padrão do observador. Este tópico mostra como implementar propriedades observáveis em C++/WinRT e como associar controles XAML a elas (para obter informações em segundo plano, confira Vinculação de dados).

Importante

Para ver conceitos e termos essenciais que ajudam a entender como utilizar e criar classes de runtime com C++/WinRT, confira Utilizar APIs com C++/WinRT e Criar APIs com C++/WinRT.

O que significa observável para uma propriedade?

Digamos que uma classe de runtime chamada BookSku tem uma propriedade chamada Title. Se BookSku acionar o evento INotifyPropertyChanged::PropertyChanged sempre que o valor de Title mudar, isso significará que Title é uma propriedade observável. É o comportamento de BookSku (acionamento ou não acionamento do evento) que determina qual de suas propriedades são observáveis, caso haja alguma.

Um elemento de texto ou controle XAML pode ser associado a esses eventos e manipulá-los. Esse elemento ou controle manipula o evento recuperando os valores atualizados e atualizando a si próprio para mostrar o novo valor.

Observação

Para obter informações sobre como instalar e usar o C++/WinRT Visual Studio Extension (VSIX) e o pacote NuGet (que juntos fornecem um modelo de projeto e suporte ao build), confira as informações de suporte do Visual Studio para C++/WinRT.

Criar um Aplicativo em branco (Bookstore)

Comece criando um novo projeto no Microsoft Visual Studio. Crie um projeto Aplicativo em branco (C++/WinRT) e nomeie-o como Bookstore. Verifique se a opção Colocar a solução e o projeto no mesmo diretório está desmarcada. Direcione a versão mais recente em disponibilidade geral (ou seja, que não esteja em versão prévia) do SDK do Windows.

Criaremos uma nova classe para representar um livro que tem uma propriedade de título observável. Criaremos e utilizaremos a classe dentro da mesma unidade de compilação. Entretanto, queremos poder criar associações nessa classe do XAML e, por isso, ela será uma classe de runtime. E usaremos o C++/WinRT para criar e usar.

A primeira etapa na criação de uma nova classe de runtime é adicionar um novo item Midl File (.idl) ao projeto. Dê ao novo item o nome BookSku.idl. Exclua o conteúdo padrão do BookSku.idl e cole esta declaração de classe de runtime.

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

Observação

Suas classes de modelo de exibição, na verdade, qualquer classe de runtime que você declarar em seu aplicativo, não precisam derivar de uma classe base. A classe BookSku declarada acima é um exemplo disso. Ela implementa uma interface, mas não deriva de qualquer classe base.

Qualquer classe de runtime declarada em seu aplicativo, que é derivada de uma classe base, é conhecida como uma classe combinável. E há restrições sobre as classes combináveis. Para um aplicativo ser aprovado nos testes do Kit de Certificação de Aplicativos Windows usados pelo Visual Studio e pela Microsoft Store para validar os envios (e, portanto, para que o aplicativo seja processado com êxito na Microsoft Store), uma classe combinável deve derivar de uma classe base do Windows. Isso significa que a classe na própria raiz da hierarquia de herança deve ser um tipo que origina em um namespace Windows.*. Se for necessário derivar uma classe de runtime de uma classe base (por exemplo, para implementar uma classe BindableBase da qual todos os seus modelos de exibição devem derivar), então é possível derivar do Windows.UI.Xaml.DependencyObject.

Um modelo de exibição é uma abstração de uma exibição e está vinculado diretamente ao modo de exibição (a marcação XAML). Um modelo de dados é uma abstração de dados, é consumido somente por seus modelos de exibição e não está diretamente associado ao XAML. Portanto, você pode declarar os modelos de dados como classes ou structs C++, não como classes de runtime. Eles não precisam ser declarados em MIDL e sinta-se à vontade para usar qualquer hierarquia de herança desejada.

Salve o arquivo e crie o projeto. O build não será (totalmente) bem-sucedido, mas fará algumas coisas necessárias para nós. Especificamente, durante o processo de build, a ferramenta midl.exe é executada para criar um arquivo de metadados do Windows Runtime que descreve a classe de runtime (o arquivo é colocado em disco em \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Em seguida, a ferramenta cppwinrt.exe é executada para gerar arquivos de código fonte para dar suporte à criação e utilização da classe de runtime. Esses arquivos incluem stubs para ajudá-lo a começar a implementar a classe de runtime BookSku que foi declarada em seu IDL. Vamos encontrá-los em disco em um momento, mas esses stubs são \Bookstore\Bookstore\Generated Files\sources\BookSku.h e BookSku.cpp.

Agora clique com o botão direito do mouse no nó do projeto no Visual Studio e clique em Abrir Pasta no Explorador de Arquivos. Isso abre a pasta de projetos no Explorador de Arquivos. Agora você deve examinar o conteúdo da pasta \Bookstore\Bookstore\. De lá, navegue até a pasta \Generated Files\sources\ e copie os arquivos stub BookSku.h e BookSku.cpp para a área de transferência. Navegue novamente até a pasta do projeto (\Bookstore\Bookstore\) e cole os dois arquivos que você acabou de copiar. Por fim, no Gerenciador de Soluções, com o nó do projeto selecionado, certifique-se de que a opção Mostrar todos os arquivos esteja ativada. Clique com o botão direito do mouse nos arquivos de stub copiados e clique em Incluir no Projeto.

Implemente BookSku

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

Em seguida, em BookSku.h, faça essas alterações.

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

Depois de fazer essas alterações, seu BookSku.h terá esta aparência.

// 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(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Windows::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, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Windows::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 modificadora Title, verificamos se um valor está sendo definido de forma diferente do valor atual. Em caso afirmativo, atualizamos o título e também acionamos o evento INotifyPropertyChanged::PropertyChanged com um argumento igual ao nome da propriedade que foi alterado. Isso é feito para que a interface do usuário (IU) saiba qual valor de propriedade deve ser consultado novamente.

O projeto será criado novamente agora, se você quiser verificar isso.

Declare e implemente BookstoreViewModel

Nossa página principal de XAML será associada a um modelo de exibição principal. E esse modelo de exibição terá várias propriedades, incluindo uma do tipo BookSku. Nesta etapa, vamos declarar e implementar nossa classe de runtime do modelo de exibição principal.

Adicione um novo item Midl File (.idl) chamado BookstoreViewModel.idl. Confira também Como fatorar classes de runtime em arquivos MIDL (.idl).

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

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

Salve e crie (o build não será totalmente bem-sucedido, mas o motivo pelo qual estamos criando é para gerar arquivos stub novamente).

Copie BookstoreViewModel.h e BookstoreViewModel.cpp da pasta Generated Files\sources para a pasta do projeto e inclua-os no projeto. Abra os arquivos (removendo o static_assert novamente) e implemente a classe de runtime, como mostrado abaixo. Observe como, em BookstoreViewModel.h, incluímos BookSku.h, que declara o tipo de implementação de BookSku (que é winrt::Bookstore::implementation::BookSku). E removemos = default do construtor padrão.

Observação

Nas listagens abaixo para BookstoreViewModel.h e BookstoreViewModel.cpp, o código ilustra a maneira padrão de construir o membro de dados m_bookSku. Esse é o método que remonta a primeira versão do C++/WinRT, e é uma boa ideia pelo menos ter familiaridade com o padrão. Com o C++/WinRT versão 2.0 e posterior, há uma forma de construção otimizada disponível para você, conhecida como construção uniforme (confira Novidades 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;
    }
}

Observação

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

Agora o projeto será criado novamente.

Adicione uma propriedade do tipo BookstoreViewModel em MainPage

Abra MainPage.idl, que declara a classe de runtime que representa nossa página principal da interface do usuário.

  • Adicione uma diretiva import para importar BookstoreViewModel.idl.
  • Adicione uma propriedade somente leitura chamada MainViewModel, do tipo BookstoreViewModel.
  • Remova a propriedade MyProperty.
// MainPage.idl
import "BookstoreViewModel.idl";

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

Salve o arquivo. O projeto não será completamente criado ainda, mas é útil criá-lo agora porque ele gera novamente os arquivos de código-fonte nos quais a classe de runtime MainPage é implementada (\Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp). Portanto, vá em frente e compile agora mesmo. O erro de compilação que você pode esperar nesse estágio é 'MainViewModel': não é um membro de 'winrt::Bookstore::implementation::MainPage' .

Se você omitir a inclusão de BookstoreViewModel.idl (veja a lista de MainPage.idl acima), verá o erro esperando < próximo a "MainViewModel" . Outra dica é certificar-se de que você deixou todos os tipos no mesmo namespace que é mostrado nas listagens de código.

Para resolver o erro esperado, agora é necessário copiar os stubs do acessador para a propriedade MainViewModel fora dos arquivos gerados (\Bookstore\Bookstore\Generated Files\sources\MainPage.h e MainPage.cpp) e em \Bookstore\Bookstore\MainPage.h e MainPage.cpp. As etapas para fazer isso são descritas a seguir.

Em \Bookstore\Bookstore\MainPage.h, execute estas etapas.

  • Inclua 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 exibição. Observe que a função de acessador de propriedade (e o membro m_mainViewModel) é implementada em termos do tipo projetado para BookstoreViewModel (que é Bookstore::BookstoreViewModel).
  • O tipo de implementação e o aplicativo estão no mesmo projeto (unidade de compilação), então construiremos m_mainViewModel por meio da sobrecarga do construtor que usa std::nullptr_t.
  • Remova a propriedade MyProperty.

Observação

No par de listagens abaixo para MainPage.h e MainPage.cpp, o código ilustra a maneira padrão de construir o membro de dados m_mainViewModel. Na seção a seguir, mostraremos uma versão que usa a construção uniforme.

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

        Bookstore::BookstoreViewModel MainViewModel();

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

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

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

  • 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 objeto BookSku como um membro de dados privado, definindo o título dele inicialmente como L"Atticus".
  • No manipulador de eventos do botão (ClickHandler), atualize o título do livro para o título publicado dele.
  • Implemente o acessador para a propriedade MainViewModel.
  • Remova a propriedade MyProperty.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Windows::UI::Xaml;

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

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Windows::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, em MainPage.h, declare e inicialize m_mainViewModel em apenas uma etapa, conforme mostrado abaixo.

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

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

Para obter mais informações sobre a construção uniforme, confira Aceitar a construção uniforme e o acesso direto de implementação.

Associe o botão à propriedade Title

Abra MainPage.xaml, que contém a marcação XAML para a página principal da interface do usuário. Como mostrado na listagem abaixo, remova o nome do botão e altere seu valor de propriedade Content de um literal para uma expressão de associação. Observe a propriedade Mode=OneWay na expressão de associação (unidirecional do modelo de exibição na interface do usuário). Sem essa propriedade, a interface do usuário não responderá aos eventos da propriedade alterada.

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

Agora compile e execute o projeto. Clique no botão para executar o manipulador de eventos Click. Esse manipulador chama a função modificadora de título do livro, esse modificador aciona um evento para informar a interface do usuário que a propriedade Title foi alterada e o botão consulta novamente o valor dessa propriedade para atualizar seu próprio valor de Content.

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

Na versão atual de C++/WinRT, para que seja possível usar a extensão de marcação {Binding}, é preciso implementar as interfaces ICustomPropertyProvider e ICustomProperty.

Associação de elemento a elemento

Você pode associar a propriedade de um elemento XAML à propriedade de outro elemento XAML. Veja um exemplo de como isso funciona na marcação.

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

Você precisará declarar a entidade XAML nomeada myTextBox como uma propriedade somente leitura em seu arquivo Midl (.idl).

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

Isso é necessário pelo seguinte motivo. Todos os tipos que o compilador XAML precisa validar (incluindo aqueles usados em {X:Bind}) são lidos dos Metadados do Windows (WinMD). Você precisa apenas adicionar a propriedade somente leitura ao arquivo Midl. Não implemente-a, porque o code-behind XAML gerado automaticamente fornece a implementação para você.

Como consumir objetos por meio da marcação XAML

Todas as entidades consumidas com o uso da extensão de marcação XAML {x:Bind} precisam ser expostas publicamente em IDL. Além disso, se a marcação XAML contiver uma referência a outro elemento que também esteja na marcação, o getter dessa marcação deverá estar presente em 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 por meio da associação. Portanto, a IDL dessa página precisa declarar uma propriedade somente leitura chamada UseCustomColorCheckBox para que ela seja acessível à associação.

O representante do manipulador de eventos de clique de UseCustomColorCheckBox usa a sintaxe clássica de representante do XAML, de modo que ele não precise de uma entrada na IDL; ele só precisa ser público na classe de implementação. Por outro lado, ChangeColorButton também tem um manipulador de eventos de clique {x:Bind}, que também precisa entrar na IDL.

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

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

Você não precisa fornecer uma implementação para a propriedade UseCustomColorCheckBox. O gerador de código XAML faz isso para você.

Associação a um booliano

Você pode fazer isso em um modo de diagnóstico:

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

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

Em vez disso, use x:Bind ao fazer a associação a um booliano.

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

Usando as Bibliotecas de Implementação do Windows (WIL)

As Bibliotecas de Implementação do Windows (WIL) oferecem auxiliares para facilitar a gravação de propriedades vinculáveis. Consulte Notificar propriedades na documentação da WIL.

APIs importantes