Implementar um provedor de widget em um aplicativo win32 (C++/WinRT)

Este artigo explica como criar um provedor de widget simples que implemente a interface IWidgetProvider. Os métodos dessa interface são invocados pelo host do widget para solicitar os dados que definem um widget ou para permitir que o provedor de widget responda a uma ação do usuário em um widget. Os provedores de widget podem dar suporte a um só widget ou a vários widgets. Neste exemplo, definiremos dois widgets diferentes. Um widget é de clima fictício que ilustra algumas das opções de formatação fornecidas pela estrutura Cartões Adaptáveis. O segundo widget demonstrará as ações do usuário e o recurso de estado do widget personalizado mantendo um contador incrementado sempre que o usuário clicar em um botão exibido no widget.

Uma captura de tela de um widget de clima simples. O widget mostra alguns gráficos e dados relacionados ao clima, bem como algum texto de diagnóstico que ilustra que o modelo para o widget de tamanho médio está sendo exibido.

Uma captura de tela de um widget de contagem simples. O widget mostra uma cadeia de caracteres contendo o valor numérico a ser incrementado e o botão Incrementar, além de um texto de diagnóstico ilustrando que o modelo do widget de tamanho pequeno está exibido.

O código de exemplo neste artigo foi adaptado do exemplo de widgets do SDK do Aplicativo Windows. Para implementar um provedor de widget usando C#, confira Implementar um provedor de widget em um aplicativo win32 (C#).

Pré-requisitos

  • O dispositivo precisa ter o modo de desenvolvedor habilitado. Para obter mais informações, confira Habilitar o dispositivo para desenvolvimento.
  • Visual Studio 2022 ou posterior com a carga de trabalho desenvolvimento da Plataforma Universal do Windows. Adicione o componente para C++ (v143) na lista suspensa opcional.

Criar um aplicativo de console win32 do C++/WinRT

No Visual Studio, crie um novo projeto. Na caixa de diálogo Criar um projeto, defina o filtro de idioma como "C++" e o filtro de plataforma como Windows e, em seguida, selecione o modelo de projeto Aplicativo de Console do Windows (C++/WinRT). Nomeie o novo projeto como "ExampleWidgetProvider". Quando solicitado, defina a versão de destino do Windows para o aplicativo como 1809 ou posterior.

Adicionar referências aos pacotes NuGet da Biblioteca de Implementação do Windows e do SDK do Aplicativo Windows

Este exemplo usa o pacote NuGet do SDK do Aplicativo Windows estável mais recente. No Gerenciador de Soluções, clique com o botão direito do mouse em Referências e selecione Gerenciar pacotes NuGet.... No gerenciador de pacotes NuGet, selecione a guia Procurar e pesquise "Microsoft.WindowsAppSDK". Selecione a versão estável mais recente na lista suspensa Versão e clique em Instalar.

Este exemplo também usa o pacote NuGet da Biblioteca de Implementação do Windows. No Gerenciador de Soluções, clique com o botão direito do mouse em Referências e selecione Gerenciar pacotes NuGet.... No gerenciador de pacotes NuGet, selecione a guia Procurar e pesquise "Microsoft.Windows.ImplementationLibrary". Selecione a versão mais recente na lista suspensa Versão e clique em Instalar.

No arquivo de cabeçalho pré-compilado, pch.h, adicione as diretivas include a seguir.

//pch.h 
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>

Observação

Você precisa incluir o cabeçalho wil/cppwinrt.h antes de qualquer cabeçalho WinRT.

Para lidar com o desligamento correto do aplicativo do provedor de widget, precisamos de uma implementação personalizada de winrt::get_module_lock. Declaramos previamente o método SignalLocalServerShutdown que será definido no arquivo main.cpp e definirá um evento que sinaliza a saída do aplicativo. Adicione o código a seguir ao arquivo pch.h, logo abaixo da #pragma once diretiva, antes dos outros includes.

//pch.h
#include <stdint.h>
#include <combaseapi.h>

// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();

namespace winrt
{
    inline auto get_module_lock() noexcept
    {
        struct service_lock
        {
            uint32_t operator++() noexcept
            {
                return ::CoAddRefServerProcess();
            }

            uint32_t operator--() noexcept
            {
                const auto ref = ::CoReleaseServerProcess();

                if (ref == 0)
                {
                    SignalLocalServerShutdown();
                }
                return ref;
            }
        };

        return service_lock{};
    }
}


#define WINRT_CUSTOM_MODULE_LOCK

Adicionar uma classe WidgetProvider para lidar com operações de widget

No Visual Studio, clique com o botão direito do mouse no projeto ExampleWidgetProvider no Gerenciador de Soluções e selecione Adicionar –> Classe. No diálogo Adicionar classe, nomeie a classe como "WidgetProvider" e clique em Adicionar.

Declarar uma classe que implemente a interface IWidgetProvider

A interface IWidgetProvider define métodos que o host do widget invocará para iniciar operações com o provedor de widget. Substitua a definição de classe vazia no arquivo WidgetProvider.h pelo código a seguir. Esse código declara uma estrutura que implementa a interface IWidgetProvider e declara protótipos para os métodos de interface.

// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
    WidgetProvider();

    /* IWidgetProvider required functions that need to be implemented */
    void CreateWidget(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState);
    void OnActionInvoked(winrt::Microsoft::Windows::Widgets::Providers::WidgetActionInvokedArgs actionInvokedArgs);
    void OnWidgetContextChanged(winrt::Microsoft::Windows::Widgets::Providers::WidgetContextChangedArgs contextChangedArgs);
    void Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void Deactivate(winrt::hstring widgetId);
    /* IWidgetProvider required functions that need to be implemented */

    
};

Além disso, adicione um método privado, UpdateWidget, que é um método auxiliar que enviará atualizações do provedor para o host do widget.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

Preparação para acompanhar widgets habilitados

Um provedor de widget pode dar suporte a um só widget ou a vários widgets. Sempre que o host do widget inicia uma operação com o provedor de widget, ele passa uma ID para identificar o widget associado à operação. Cada widget também tem um nome associado e um valor de estado que podem ser usados para armazenar dados personalizados. Neste exemplo, vamos declarar uma estrutura auxiliar simples para armazenar a ID, o nome e os dados de cada widget fixado. Os widgets também podem estar em um estado ativo, que é discutido na seção Ativar e Desativar abaixo, e vamos acompanhar esse estado para cada widget com um valor booliano. Adicione a definição a seguir ao arquivo WidgetProvider.h, acima da declaração de struct WidgetProvider.

// WidgetProvider.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
};

Dentro da declaração WidgetProvider em WidgetProvider.h, adicione um membro para o mapa que manterá a lista de widgets habilitados, usando a ID do widget como a chave de cada entrada.

// WidgetProvider.h
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
    private:
        ...
        static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;

        

Declarar cadeias de caracteres JSON do modelo de widget

Este exemplo vai declarar algumas cadeias de caracteres estáticas para definir os modelos JSON de cada widget. Para facilitar o trabalho, esses modelos são armazenados nas variáveis locais declaradas fora da definição de classe WidgetProvider. Se você precisar de um armazenamento geral para os modelos, eles poderão ser incluídos como parte do pacote de aplicativos: Acessar arquivos de pacote. Para obter informações de como criar o documento JSON do modelo de widget, confira Criar um modelo de widget com o Designer de Cartão Adaptável.

// WidgetProvider.h
const std::string weatherWidgetTemplate = R"(
{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
    "backgroundImage": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg",
    "body": [
        {
            "type": "TextBlock",
            "text": "Redmond, WA",
            "size": "large",
            "isSubtle": true,
            "wrap": true
        },
        {
            "type": "TextBlock",
            "text": "Mon, Nov 4, 2019 6:21 PM",
            "spacing": "none",
            "wrap": true
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "Image",
                            "url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
                            "size": "small",
                            "altText": "Mostly cloudy weather"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "46",
                            "size": "extraLarge",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "°F",
                            "weight": "bolder",
                            "spacing": "small",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Hi 50",
                            "horizontalAlignment": "left",
                            "wrap": true
                        },
                        {
                            "type": "TextBlock",
                            "text": "Lo 41",
                            "horizontalAlignment": "left",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                }
            ]
        }
    ]
})";

const std::string countWidgetTemplate = R"(
{                                                                     
    "type": "AdaptiveCard",                                         
    "body": [                                                         
        {                                                               
            "type": "TextBlock",                                    
            "text": "You have clicked the button ${count} times"    
        },
        {
             "text":"Rendering Only if Medium",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"medium\"}"
        },
        {
             "text":"Rendering Only if Small",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"small\"}"
        },
        {
         "text":"Rendering Only if Large",
         "type":"TextBlock",
         "$when":"${$host.widgetSize==\"large\"}"
        }                                                                    
    ],                                                                  
    "actions": [                                                      
        {                                                               
            "type": "Action.Execute",                               
            "title": "Increment",                                   
            "verb": "inc"                                           
        }                                                               
    ],                                                                  
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5"                                                
})";

Implementar os métodos IWidgetProvider

Nas próximas seções, vamos implementar os métodos da interface IWidgetProvider. O método auxiliar UpdateWidget chamado em várias dessas implementações de método será mostrado mais adiante neste artigo. Antes de se aprofundar nos métodos de interface, adicione as seguintes linhas a WidgetProvider.cpp, após as diretivas include, para efetuar pull das APIs do provedor de widget no namespace winrt e permitir o acesso ao mapa que declaramos na etapa anterior.

Observação

Os objetos passados aos métodos de retorno de chamada da interface IWidgetProvider só têm garantia de serem válidos dentro do retorno de chamada. Você não deve armazenar referências a esses objetos porque o comportamento fora do contexto do retorno de chamada é indefinido.

// WidgetProvider.cpp
namespace winrt
{
    using namespace Microsoft::Windows::Widgets::Providers;
}

std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};

CreateWidget

O host do widget chama CreateWidget quando o usuário fixa um dos widgets do aplicativo no host do widget. Primeiro, esse método obtém a ID e o nome do widget associado e adiciona uma nova instância da estrutura auxiliar, CompactWidgetInfo, à coleção de widgets habilitados. Em seguida, enviamos o modelo inicial e os dados do widget, que é encapsulado no método auxiliar UpdateWidget.

// WidgetProvider.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();
    auto widgetName = widgetContext.DefinitionId();
    CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
    RunningWidgets[widgetId] = runningWidgetInfo;
    
    // Update the widget
    UpdateWidget(runningWidgetInfo);
}

DeleteWidget

O host do widget chama DeleteWidget quando o usuário desafixa um dos widgets do aplicativo do host do widget. Quando isso ocorre, removemos o widget associado da lista de widgets habilitados para que não sejam mais enviadas atualizações desse widget.

// WidgetProvider.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
    RunningWidgets.erase(widgetId);
}

OnActionInvoked

O host do widget chama OnActionInvoked quando o usuário interage com uma ação que você definiu no modelo de widget. Para o widget de contador usado neste exemplo, uma ação foi declarada com um valor de verbo igual a "inc" no modelo JSON do widget. O código do provedor de widget usará esse valor de verbo para determinar qual ação tomar em resposta à interação do usuário.

...
    "actions": [                                                      
        {                                                               
            "type": "Action.Execute",                               
            "title": "Increment",                                   
            "verb": "inc"                                           
        }                                                               
    ], 
...

No método OnActionInvoked, obtenha o valor do verbo verificando a propriedade Verb do WidgetActionInvokedArgs passado para o método. Se o verbo for "inc", saberemos que vamos incrementar a contagem no estado personalizado para o widget. Em WidgetActionInvokedArgs, obtenha o objeto WidgetContext e, em seguida, o WidgetId para obter a ID do widget que está sendo atualizado. Localize a entrada no mapa de widgets habilitados com a ID especificada e atualize o valor de estado personalizado usado para armazenar o número de incrementos. Por fim, atualize o conteúdo do widget com o novo valor usando a função auxiliar UpdateWidget.

// WidgetProvider.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb == L"inc")
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        // If you need to use some data that was passed in after
        // Action was invoked, you can get it from the args:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Increment the count
            localWidgetInfo.customState++;
            UpdateWidget(localWidgetInfo);
        }
    }
}

Para obter informações sobre a sintaxe Action.Execute de Cartões Adaptáveis, confira Action.Execute. Para obter diretrizes de como projetar a interação dos widgets, confira Diretrizes de design de interação do widget

OnWidgetContextChanged

Na versão atual, OnWidgetContextChanged só é chamado quando o usuário altera o tamanho de um widget fixado. Você pode optar por retornar um modelo/dados JSON diferente para o host do widget, dependendo do tamanho solicitado. Você também pode criar o modelo JSON para dar suporte a todos os tamanhos disponíveis usando a renderização condicional com base no valor de host.widgetSize. Se você não precisar enviar um novo modelo ou dados para considerar a alteração de tamanho, use OnWidgetContextChanged para fins de telemetria.

// WidgetProvider.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
    auto widgetContext = contextChangedArgs.WidgetContext();
    auto widgetId = widgetContext.Id();
    auto widgetSize = widgetContext.Size();
    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto localWidgetInfo = iter->second;

        UpdateWidget(localWidgetInfo);
    }
}
    

Activate e Deactivate

O método Activate é chamado para notificar o provedor de widget de que o host do widget quer receber conteúdo atualizado do provedor no momento. Por exemplo, isso pode significar que o usuário está exibindo ativamente o host do widget no momento. O método Deactivate é chamado para notificar o provedor de widget de que o host do widget não está mais solicitando atualizações de conteúdo. Esses dois métodos definem uma janela na qual o host do widget prefere mostrar o conteúdo mais atualizado. Os provedores de widget podem enviar atualizações para o widget a qualquer momento, como em resposta a uma notificação por push, mas, como em qualquer tarefa em segundo plano, é importante equilibrar o fornecimento de conteúdo atualizado com questões de recursos, como a duração da bateria.

Activate e Deactivate são chamados por widget. Este exemplo acompanha o status ativo de cada widget no struct auxiliar CompactWidgetInfo. No método Activate, chamamos o método auxiliar UpdateWidget para atualizar o widget. Observe que a janela de tempo entre Activate e Deactivate pode ser pequena, portanto, é recomendável que você tente acelerar ao máximo o caminho do código de atualização do widget.

void WidgetProvider::Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();

    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        localWidgetInfo.isActive = true;

        UpdateWidget(localWidgetInfo);
    }
}

void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        localWidgetInfo.isActive = false;
    }
}

Atualizar um widget

Defina o método auxiliar UpdateWidget para atualizar um widget habilitado. Neste exemplo, marcamos o nome do widget no struct auxiliar CompactWidgetInfo passado ao método e, em seguida, definimos o modelo apropriado e o JSON de dados com base no widget que está sendo atualizado. Um WidgetUpdateRequestOptions é inicializado com o modelo, os dados e o estado personalizado do widget que está sendo atualizado. Chame WidgetManager::GetDefault para obter uma instância da classe WidgetManager e, em seguida, chame UpdateWidget para enviar os dados atualizados do widget ao host do widget.

// WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    winrt::WidgetUpdateRequestOptions updateOptions{ localWidgetInfo.widgetId };

    winrt::hstring templateJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        templateJson = winrt::to_hstring(weatherWidgetTemplate);
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        templateJson = winrt::to_hstring(countWidgetTemplate);
    }

    winrt::hstring dataJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        dataJson = L"{}";
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        dataJson = L"{ \"count\": " + winrt::to_hstring(localWidgetInfo.customState) + L" }";
    }

    updateOptions.Template(templateJson);
    updateOptions.Data(dataJson);
    // You can store some custom state in the widget service that you will be able to query at any time.
    updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
    winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}

Inicializar a lista de widgets habilitados na inicialização

Quando o provedor de widget é inicializado pela primeira vez, é bom perguntar ao WidgetManager se há widgets em execução que o provedor está fornecendo no momento. Isso ajudará a recuperar o aplicativo para o estado anterior em caso de reinicialização do computador ou falha do provedor. Chame WidgetManager::GetDefault para obter a instância do gerenciador de widget padrão do aplicativo. Em seguida, chame GetWidgetInfos, que retorna uma matriz de objetos WidgetInfo. Copie as IDs, os nomes e o estado personalizado do widget no struct auxiliar CompactWidgetInfo e salve-o na variável de membro RunningWidgets. Cole o código a seguir no construtor da classe WidgetProvider.

// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
    auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
    for (auto widgetInfo : runningWidgets )
    {
        auto widgetContext = widgetInfo.WidgetContext();
        auto widgetId = widgetContext.Id();
        auto widgetName = widgetContext.DefinitionId();
        auto customState = widgetInfo.CustomState();
        if (RunningWidgets.find(widgetId) == RunningWidgets.end())
        {
            CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
            try
            {
                // If we had any save state (in this case we might have some state saved for Counting widget)
                // convert string to required type if needed.
                int count = std::stoi(winrt::to_string(customState));
                runningWidgetInfo.customState = count;
            }
            catch (...)
            {

            }
            RunningWidgets[widgetId] = runningWidgetInfo;
        }
    }
}

Registrar uma fábrica de classes que criará uma instância de WidgetProvider na solicitação

Adicione o cabeçalho que define a classe WidgetProvider aos includes na parte superior do arquivo do aplicativo main.cpp. Também incluiremos um mutex aqui.

// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>

Declare o evento que vai disparar o encerramento do aplicativo e a função SignalLocalServerShutdown que vai definir o evento. Cole o código a seguir em main.cpp.

// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);

void SignalLocalServerShutdown()
{
    g_shudownEvent.SetEvent();
}

Em seguida, você precisará criar um CLSID que será usado para identificar o provedor de widget para ativação do COM. Gere um GUID no Visual Studio acessando Ferramentas –> Criar GUID. Selecione a opção "static const GUID =", clique em Copiar e cole-a em main.cpp. Atualize a definição de GUID com a sintaxe C++/WinRT a seguir, definindo o nome da variável GUID como widget_provider_clsid. Deixe a versão comentada do GUID porque você precisará desse formato mais tarde, ao empacotar o aplicativo.

// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
    0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};

Adicione a seguinte definição de fábrica de classe a main.cpp. Esse é principalmente um código clichê que não é específico de implementações do provedor de widget. Observe que CoWaitForMultipleObjects aguarda que o evento de desligamento seja disparado antes do encerramento do aplicativo.

// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
    STDMETHODIMP CreateInstance(
        ::IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        std::unique_lock lock(mutex);

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        if (!instance)
        {
            instance = winrt::make<WidgetProvider>();
        }

        return instance.as(iid, result);
    }

    STDMETHODIMP LockServer(BOOL) noexcept final
    {
        return S_OK;
    }

private:
    T instance{ nullptr };
    std::mutex mutex;
};

int main()
{
    winrt::init_apartment();
    wil::unique_com_class_object_cookie widgetProviderFactory;
    auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>>();

    winrt::check_hresult(CoRegisterClassObject(
        widget_provider_clsid,
        factory.get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_MULTIPLEUSE,
        widgetProviderFactory.put()));

    DWORD index{};
    HANDLE events[] = { g_shudownEvent.get() };
    winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
        INFINITE,
        static_cast<ULONG>(std::size(events)), events, &index));

    return 0;
}

Empacotar o aplicativo de provedor de widget

Na versão atual, somente aplicativos empacotados podem ser registrados como provedores de widget. As etapas a seguir mostram o processo de empacotamento do aplicativo e atualização do manifesto do aplicativo para registrar o aplicativo no sistema operacional como um provedor de widget.

Criar um projeto de empacotamento MSIX

No Gerenciador de Soluções, clique com o botão direito do mouse na solução e selecione Adicionar –> Novo Projeto.... Na caixa de diálogo Adicionar um novo projeto, selecione o modelo "Projeto de Empacotamento de Aplicativo do Windows" e clique em Avançar. Defina o nome do projeto como "ExampleWidgetProviderPackage" e clique em Criar. Quando solicitado, defina a versão de destino como 1809 ou posterior e clique em OK. Em seguida, clique com o botão direito do mouse no projeto ExampleWidgetProviderPackage e selecione Adicionar –> Referência do projeto. Selecione o projeto ExampleWidgetProvider e clique em OK.

Adicionar a referência de pacote do SDK do Aplicativo Windows ao projeto de empacotamento

Você precisa adicionar uma referência ao pacote NuGet do SDK do Aplicativo Windows ao projeto de empacotamento MSIX. No Gerenciador de Soluções, clique duas vezes no projeto ExampleWidgetProviderPackage para abrir o arquivo ExampleWidgetProviderPackage.wapproj. Adicione o XML a seguir dentro do elemento Project.

<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
        <IncludeAssets>build</IncludeAssets>
    </PackageReference>  
</ItemGroup>

Observação

Verifique se a Version especificada no elemento PackageReference corresponde à versão estável mais recente referenciada na etapa anterior.

Se a versão correta do SDK do Aplicativo Windows já estiver instalada no computador e você não quiser inserir o runtime do SDK no pacote, especifique a dependência do pacote no arquivo Package.appxmanifest do projeto ExampleWidgetProviderPackage.

<!--Package.appxmanifest-->
...
<Dependencies>
...
    <PackageDependency Name="Microsoft.WindowsAppRuntime.1.2-preview2" MinVersion="2000.638.7.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
...
</Dependencies>
...

Atualizar o manifesto do pacote

No Gerenciador de Soluções clique com o botão direito do mouse no arquivo Package.appxmanifest e selecione Exibir Código para abrir o arquivo XML de manifesto. Em seguida, você precisa adicionar algumas declarações de namespace para as extensões do pacote do aplicativo que usaremos. Adicione as seguintes definições de namespace ao elemento Package de nível superior.

<!-- Package.appmanifest -->
<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"

Dentro do elemento Application, crie um elemento vazio chamado Extensions. Ele deve estar após a marca de fechamento de uap:VisualElements.

<!-- Package.appxmanifest -->
<Application>
...
    <Extensions>

    </Extensions>
</Application>

A primeira extensão que precisamos adicionar é a ComServer. Isso registra o ponto de entrada do executável no sistema operacional. Essa extensão é o aplicativo empacotado equivalente ao registro de um servidor COM por meio da definição de uma chave do Registro e não é específica para provedores de widget. Adicione o seguinte elemento com:Extension como um filho do elemento Extensions. Altere o GUID no atributo Id do elemento com:Class do GUID gerado em uma etapa anterior.

<!-- Package.appxmanifest -->
<Extensions>
    <com:Extension Category="windows.comServer">
        <com:ComServer>
            <com:ExeServer Executable="ExampleWidgetProvider\ExampleWidgetProvider.exe" DisplayName="ExampleWidgetProvider">
                <com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="ExampleWidgetProvider" />
            </com:ExeServer>
        </com:ComServer>
    </com:Extension>
</Extensions>

Em seguida, adicione a extensão que registra o aplicativo como um provedor de widget. Cole o elemento uap3:Extension no snippet de código a seguir, como um filho do elemento Extensions. Substitua o atributo ClassId do elemento COM pelo GUID usado nas etapas anteriores.

<!-- Package.appxmanifest -->
<Extensions>
    ...
    <uap3:Extension Category="windows.appExtension">
        <uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="WidgetTestApp" Id="ContosoWidgetApp" PublicFolder="Public">
            <uap3:Properties>
                <WidgetProvider>
                    <ProviderIcons>
                        <Icon Path="Images\StoreLogo.png" />
                    </ProviderIcons>
                    <Activation>
                        <!-- Apps exports COM interface which implements IWidgetProvider -->
                        <CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
                    </Activation>

                    <TrustedPackageFamilyNames>
                        <TrustedPackageFamilyName>Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe</TrustedPackageFamilyName>
                    </TrustedPackageFamilyNames>

                    <Definitions>
                        <Definition Id="Weather_Widget"
                            DisplayName="Weather Widget"
                            Description="Weather Widget Description"
                            AllowMultiple="true">
                            <Capabilities>
                                <Capability>
                                    <Size Name="small" />
                                </Capability>
                                <Capability>
                                    <Size Name="medium" />
                                </Capability>
                                <Capability>
                                    <Size Name="large" />
                                </Capability>
                            </Capabilities>
                            <ThemeResources>
                                <Icons>
                                    <Icon Path="ProviderAssets\Weather_Icon.png" />
                                </Icons>
                                <Screenshots>
                                    <Screenshot Path="ProviderAssets\Weather_Screenshot.png" DisplayAltText="For accessibility" />
                                </Screenshots>
                                <!-- DarkMode and LightMode are optional -->
                                <DarkMode />
                                <LightMode />
                            </ThemeResources>
                        </Definition>
                        <Definition Id="Counting_Widget"
                                DisplayName="Microsoft Counting Widget"
                                Description="Couting Widget Description">
                            <Capabilities>
                                <Capability>
                                    <Size Name="small" />
                                </Capability>
                            </Capabilities>
                            <ThemeResources>
                                <Icons>
                                    <Icon Path="ProviderAssets\Counting_Icon.png" />
                                </Icons>
                                <Screenshots>
                                    <Screenshot Path="ProviderAssets\Counting_Screenshot.png" DisplayAltText="For accessibility" />
                                </Screenshots>
                                <!-- DarkMode and LightMode are optional -->
                                <DarkMode>

                                </DarkMode>
                                <LightMode />
                            </ThemeResources>
                        </Definition>
                    </Definitions>
                </WidgetProvider>
            </uap3:Properties>
        </uap3:AppExtension>
    </uap3:Extension>
</Extensions>

Para obter descrições detalhadas e informações de formato de todos esses elementos, confira Formato XML do manifesto do pacote do provedor de widget.

Adicionar ícones e outras imagens ao projeto de empacotamento

No Gerenciador de Soluções, clique com o botão direito do mouse em ExampleWidgetProviderPackage e selecione Adicionar –> Nova Pasta. Nomeie essa pasta como ProviderAssets, pois esse nome foi usado no Package.appxmanifest da etapa anterior. É aqui que armazenaremos os ícones e as capturas de tela dos widgets. Depois de adicionar os ícones e as capturas de tela desejados, verifique se os nomes de imagem correspondem ao que vem após Path=ProviderAssets\ no Package.appxmanifest ou os widgets não aparecerão no host do widget.

Para obter informações sobre os requisitos de design de imagens de captura de tela e as convenções de nomenclatura de capturas de tela localizadas, confira Integrar com o seletor de widget.

Testar o provedor de widget

Verifique se você selecionou a arquitetura que corresponde ao computador de desenvolvimento na lista suspensa Plataformas de Solução, por exemplo, "x64". No Gerenciador de Soluções, clique com o botão direito do mouse na solução e selecione Compilar Solução. Depois que isso for feito, clique com o botão direito do mouse em ExampleWidgetProviderPackage e selecione Implantar. Na versão atual, o único host de widget com suporte é o Quadro de Widgets. Para ver os widgets, você precisará abrir o Quadro de Widgets e selecionar Adicionar widgets no canto superior direito. Role até a parte inferior dos widgets disponíveis e veja o Widget Clima fictício e o Widget de Contagem da Microsoft que foram criados neste tutorial. Clique nos widgets para fixá-los no quadro de widgets e testar a funcionalidade deles.

Depurar o provedor de widget

Depois de fixar os widgets, a Plataforma de Widget iniciará o aplicativo do provedor de widget para receber e enviar informações relevantes sobre o widget. Para depurar o widget em execução, você pode anexar um depurador ao aplicativo do provedor de widget em execução ou configurar o Visual Studio para iniciar automaticamente a depuração do processo do provedor de widget depois que ele for iniciado.

Para anexá-lo ao processo em execução:

  1. No Visual Studio, clique em Depurar –> Anexar ao processo.
  2. Filtre os processos e localize o aplicativo do provedor de widget desejado.
  3. Anexe o depurador.

Para anexar automaticamente o depurador ao processo quando ele for iniciado pela primeira vez:

  1. No Visual Studio, escolha Depurar –> Outros Destinos de Depuração> Depurar Pacote de Aplicativo Instalado.
  2. Filtre os pacotes e localize o pacote do provedor de widget desejado.
  3. Selecione-o e marque a caixa "Não iniciar, mas depurar o código quando ele for iniciado".
  4. Clique em Anexar.

Converter o aplicativo de console em um aplicativo do Windows

Para converter o aplicativo de console criado neste passo a passo em um aplicativo do Windows:

  1. Clique com o botão direito do mouse no projeto ExampleWidgetProvider no Gerenciador de Soluções e selecione Propriedades. Navegue até Vinculador –> Sistema e altere Subsistema de "Console" para "Windows". Isso também pode ser feito adicionando <SubSystem>Windows</SubSystem> à seção <Link>..</Link> do .vcxproj.
  2. Em main.cpp, altere int main() para int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

Uma captura de tela que mostra as propriedades do projeto de provedor de widget C++ com o tipo de saída definido como Aplicativo do Windows

Publicar o widget

Depois de desenvolver e testar o widget, você pode publicar o aplicativo na Microsoft Store para que os usuários instalem os widgets nos dispositivos. Para obter diretrizes passo a passo de como publicar um aplicativo, confira Publicar o aplicativo na Microsoft Store.

A coleção de repositórios de widgets

Depois que o aplicativo for publicado na Microsoft Store, você poderá solicitar que ele seja incluído na Coleção da Store de widgets que ajuda os usuários a descobrir aplicativos que apresentam Widgets do Windows. Para enviar sua solicitação, confira Enviar informações do widget para adição à Coleção da Store.

Captura de tela da Microsoft Store mostrando a coleção de widgets que permite que os usuários descubram aplicativos que apresentam Widgets do Windows.

Implementando a personalização do widget

A partir do SDK do Aplicativo do Windows 1.4, os widgets podem dar suporte à personalização pelo usuário. Quando esse recurso é implementado, uma opção Personalizar widget é adicionada ao menu de reticências acima da opção Desafixar widget.

Uma captura de tela mostrando um widget com a caixa de diálogo de personalização exibida.

As etapas a seguir resumem o processo de personalização do widget.

  1. Em operação normal, o provedor do widget responde às solicitações do host do widget com os modelos JSON de dados e visuais para a experiência regular.
  2. O usuário clica no botão Personalizar widget no menu de reticências.
  3. O widget gera o evento OnCustomizationRequested no provedor do widget para indicar que o usuário solicitou a experiência de personalização.
  4. O provedor define um sinalizador interno para indicar que o widget está no modo de personalização. Enquanto está no modo de personalização, o provedor do widget envia os modelos JSON para a interface do usuário de personalização do widget em vez da regular.
  5. Enquanto está no modo de personalização, o provedor do widget recebe eventos OnActionInvoked à medida que o usuário interage com a interface do usuário de personalização e ajusta sua configuração e comportamento internos com base nas ações do usuário.
  6. Quando a ação associada ao evento OnActionInvoked é a ação de "personalização de saída" definida pelo aplicativo, o provedor do widget redefine seu sinalizador interno para indicar que ele não está mais no modo de personalização e retoma o envio dos modelos JSON de dados e visuais para a experiência regular do widget, refletindo as alterações solicitadas durante a personalização.
  7. O provedor do widget persiste as opções de personalização para o disco ou para a nuvem a fim de que as alterações sejam preservadas entre suas invocações.

Observação

Há um bug conhecido com o Windows Widget Board para widgets criados usando o SDK do Aplicativo do Windows, que faz com que o menu de reticências pare de responder depois que o cartão de personalização é mostrado.

Em cenários típicos de personalização do Widget, o usuário escolherá quais dados são exibidos no widget ou ajustará a apresentação visual do widget. Para simplificar, o exemplo nesta seção adicionará o comportamento de personalização que permite ao usuário redefinir o contador do widget de contagem implementado nas etapas anteriores.

Observação

A personalização do widget só tem suporte no SDK do Aplicativo do Windows 1.4 e posterior. Certifique-se de atualizar as referências em seu projeto para a última versão do pacote Nuget.

Atualizar o manifesto do pacote para declarar suporte à personalização

Para permitir que o host do widget saiba que o widget dá suporte à personalização, adicione o atributo IsCustomizable ao elemento Definição para o widget e defina-o como true.

...
<Definition Id="Counting_Widget"
    DisplayName="Microsoft Counting Widget"
    Description="CONFIG counting widget description"
    IsCustomizable="true">
...

Atualizar WidgetProvider.h

Para adicionar suporte de personalização ao widget criado nas etapas anteriores desse artigo, precisaremos atualizar o arquivo de cabeçalho para o provedor do nosso widget, WidgetProvider.h.

Primeiro, atualize a definição CompactWidgetInfo. Esse struct auxiliar nos ajuda a acompanhar o estado atual de nossos widgets ativos. Adicione o campo inCustomization, que será usado para acompanhar quando o host do widget espera que enviemos nosso modelo json de personalização em vez do modelo de widget regular.

// WidgetProvider.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
    bool inCustomization = false;
};

Atualize a declaração WidgetProvider para implementar a interface IWidgetProvider2.

// WidgetProvider.h

struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider2>

Adicione uma declaração para o retorno de chamada OnCustomizationRequested da interface IWidgetProvider2.

// WidgetProvider.h

void OnCustomizationRequested(winrt::Microsoft::Windows::Widgets::Providers::WidgetCustomizationRequestedArgs args);

Por fim, declare uma variável de cadeia de caracteres que define o modelo JSON para a interface do usuário de personalização do widget. Para este exemplo, temos um botão "Redefinir contador" e um botão "Sair da personalização" que sinalizarão nosso provedor para retornar ao comportamento regular do widget.

// WidgetProvider.h
const std::string countWidgetCustomizationTemplate = R"(
{
    "type": "AdaptiveCard",
    "actions" : [
        {
            "type": "Action.Execute",
            "title" : "Reset counter",
            "verb": "reset"
            },
            {
            "type": "Action.Execute",
            "title": "Exit customization",
            "verb": "exitCustomization"
            }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5"
})";

Atualizar WidgetProvider.cpp

Agora atualize o arquivo WidgetProvider.cpp para implementar o comportamento de personalização do widget. Esse método usa o mesmo padrão que os outros retornos de chamada que usamos. Obtemos a ID do widget a ser personalizada do WidgetContext e localizamos o struct auxiliar CompactWidgetInfo associado a esse widget e definimos o campo inCustomization como true.

//WidgetProvider.cpp
void WidgetProvider::OnCustomizationRequested(winrt::WidgetCustomizationRequestedArgs args)
{
    auto widgetId = args.WidgetContext().Id();

    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        localWidgetInfo.inCustomization = true;

        UpdateWidget(localWidgetInfo);
    }
}

Em seguida, atualizaremos nosso método auxiliar UpdateWidget que envia nossos modelos JSON de dados e visuais para o host do widget. Quando estamos atualizando o widget de contagem, enviamos o modelo de widget regular ou o modelo de personalização, dependendo do valor do campo inCustomization. Para fins de brevidade, o código não relevante para a personalização é omitido neste snippet de código.

//WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    ...
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        if (!localWidgetInfo.inCustomization)
        {
            std::wcout << L" - not in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetTemplate);
		}
        else
        {
            std::wcout << L" - in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetCustomizationTemplate);
		}
    }
    ...
    
    updateOptions.Template(templateJson);
    updateOptions.Data(dataJson);
    // !!  You can store some custom state in the widget service that you will be able to query at any time.
    updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
    winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}

Quando os usuários interagem com entradas em nosso modelo de personalização, ele chama o mesmo manipulador OnActionInvoked de quando o usuário interage com a experiência regular do widget. Para dar suporte à personalização, procuramos os verbos "reset" e "exitCustomization" de nosso modelo JSON de personalização. Se a ação for para o botão "Redefinir contador", redefiniremos o contador mantido no campo customState do nosso struct auxiliar para 0. Se a ação for para o botão "Sair da personalização", definiremos o campo inCustomization como false para que, quando chamarmos UpdateWidget, nosso método auxiliar envie os modelos JSON regulares, e não o modelo de personalização.

//WidgetProvider.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb == L"inc")
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        // If you need to use some data that was passed in after
        // Action was invoked, you can get it from the args:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Increment the count
            localWidgetInfo.customState++;
            UpdateWidget(localWidgetInfo);
        }
    }
    else if (verb == L"reset") 
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Reset the count
            localWidgetInfo.customState = 0;
            localWidgetInfo.inCustomization = false;
            UpdateWidget(localWidgetInfo);
        }
    }
    else if (verb == L"exitCustomization")
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        // If you need to use some data that was passed in after
        // Action was invoked, you can get it from the args:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Stop sending the customization template
            localWidgetInfo.inCustomization = false;
            UpdateWidget(localWidgetInfo);
        }
    }
}

Agora, ao implantar o widget, você deverá ver o botão Personalizar widget no menu de reticências. Clicar no botão Personalizar exibirá seu modelo de personalização.

Uma captura de tela mostrando a IU de personalização de widgets.

Clique no botão Redefinir contador para redefinir o contador para 0. Clique no botão Sair da personalização para retornar ao comportamento regular do widget.