Partilhar via


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

Este artigo orienta-o na criação de um provedor de widget simples que implementa 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 widgets responda a uma ação do usuário em um widget. Os provedores de widgets podem suportar um único widget ou vários widgets. Neste exemplo, definiremos dois widgets diferentes. Um dos widgets é um widget de clima simulado que ilustra algumas opções de formatação fornecidas pela estrutura Adaptive Cards. O segundo widget demonstrará as ações do usuário e o recurso de estado do widget personalizado, mantendo um contador que é incrementado sempre que o usuário clica em um botão exibido no widget.

Uma captura de tela de um widget meteorológico simples. O widget mostra alguns gráficos e dados relacionados ao clima, bem como algum texto de diagnóstico ilustrando 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 um botão chamado Incremento, bem como algum texto de diagnóstico ilustrando que o modelo para o widget de tamanho pequeno está sendo exibido.

Este código de exemplo neste artigo é adaptado do Windows App SDK Widgets Sample. Para implementar um provedor de widget usando C#, consulte Implementar um provedor de widget em um aplicativo win32 (C#).

Pré-requisitos

  • Seu dispositivo deve ter o modo de desenvolvedor ativado. Para obter mais informações, consulte Configurações para desenvolvedores.
  • Visual Studio 2022 ou posterior com o desenvolvimento da Plataforma Universal do Windows carga de trabalho. Certifique-se de adicionar o componente para C++ (v143) na lista suspensa opcional.

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

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

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

Este exemplo usa o pacote NuGet estável mais recente do SDK de Aplicativo Windows. No Explorador de Soluções, clique com o botão direito do rato em Referências e selecione Gerir pacotes NuGet.... No gestor de pacotes NuGet, selecione o separador Procurar e procure por "Microsoft.WindowsAppSDK". Selecione a versão estável mais recente no menu suspenso Versão e clique em Instalar.

Este exemplo também usa o pacote NuGet da Biblioteca de Implementação do Windows. No Explorador de Soluções, clique direito em Referências e selecione Gerir pacotes NuGet.... No gerenciador de pacotes NuGet, selecione a guia Procurar e pesquise por "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 seguintes diretivas de inclusão.

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

Observação

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

A fim de lidar com o desligamento do aplicativo provedor de widget corretamente, precisamos de uma implementação personalizada de winrt::get_module_lock. Nós pré-declaramos o método SignalLocalServerShutdown que será definido em nosso arquivo de main.cpp e definirá um evento que sinaliza o aplicativo para sair. Adicione o seguinte código ao seu ficheiro pch.h, logo abaixo da diretiva #pragma once, antes das outras inclusões.

//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>. Na caixa de diálogo Adicionar classe, nomeie a classe "WidgetProvider" e clique em Adicionar.

Declarar uma classe que implementa a interface IWidgetProvider

A interface IWidgetProvider define métodos que o host do widget invocará para iniciar operações com o provedor do widget. Substitua a definição de classe vazia no arquivo WidgetProvider.h pelo código a seguir. Este 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 nosso provedor para o host do widget.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

Prepare-se para rastrear widgets ativados

Um provedor de widgets pode suportar um único widget ou vários widgets. Sempre que o host do widget inicia uma operação com o provedor do widget, ele passa um ID para identificar o widget associado à operação. Cada widget também tem um nome associado e um valor de estado que pode ser usado para armazenar dados personalizados. Neste exemplo, declararemos uma estrutura auxiliar simples para armazenar a ID, o nome e os dados de cada widget fixo. Os widgets também podem estar em um estado ativo, o que é discutido na seção Ativar e Desativar, abaixo, e rastrearemos esse estado para cada widget com um valor booleano. Adicione a seguinte definição ao ficheiro 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 o ID do widget como a chave para 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 declarará algumas cadeias de caracteres estáticas para definir os modelos JSON para cada widget. Por conveniência, esses modelos são armazenados nas variáveis locais declaradas fora da definição de classe WidgetProvider . Se precisar de um armazenamento geral para os modelos, eles podem ser incluídos como parte do pacote da aplicação: Aceder a arquivos de pacote. Para obter informações sobre como criar o documento JSON do modelo de widget, consulte Criar um modelo de widget com o Adaptive Card Designer.

Na versão mais recente, os aplicativos que implementam widgets do Windows podem personalizar o cabeçalho exibido para seu widget no Painel de widgets, substituindo a apresentação padrão. Para obter mais informações, consulte Personalizar a área de cabeçalho do widget.

Observação

Na versão mais recente, os aplicativos que implementam widgets do Windows podem optar por preencher o conteúdo do widget com HTML servido a partir de uma URL especificada em vez de fornecer conteúdo no formato de esquema Adaptive Card na carga JSON passada do provedor para o Widgets Board. Os provedores de widgets ainda devem fornecer uma carga JSON do Adaptive Card, portanto, as etapas de implementação neste passo a passo são aplicáveis aos widgets da Web. Para obter mais informações, consulte provedores de widgets da Web.

// 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, implementaremos os métodos do IWidgetProvider interface. O método auxiliar UpdateWidget que é chamado em várias dessas implementações de método será mostrado posteriormente neste artigo. Antes de mergulhar nos métodos de interface, adicione as seguintes linhas ao WidgetProvider.cpp, após as diretivas include, para puxar as APIs do provedor de widgets para o namespace winrt e permitir o acesso ao mapa declarado na etapa anterior.

Observação

Os objetos passados para os métodos de retorno de chamada da interface IWidgetProvider têm a sua validade garantida apenas dentro do retorno de chamada. Você não deve armazenar referências a esses objetos porque seu 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{};

CriarWidget

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

// 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);
}

Eliminar Widget

O host do widget chama DeleteWidget quando o utilizador retira um dos widgets do seu aplicativo do host do widget. Quando isso ocorrer, removeremos o widget associado da nossa lista de widgets ativados para que não enviemos mais atualizações para esse 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 definida no modelo de widget. Para o widget de contador usado neste exemplo, uma ação foi declarada com um verbo com o valor de "inc" no template JSON para o widget. O código do provedor de widget usará este valor do 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 dos WidgetActionInvokedArgs passados para o método. Se o verbo é "inc", então sabemos que vamos incrementar a contagem no estado personalizado para o widget. NoWidgetActionInvokedArgs, obtenha o objeto WidgetContext e, em seguida, o WidgetId para obter a ID do widget que está a ser atualizado. Encontre a entrada em nosso mapa de widgets habilitados com a ID especificada e, em seguida, atualize o valor de estado personalizado usado para armazenar o número de incrementos. Finalmente, use a função auxiliar UpdateWidget para atualizar o conteúdo do widget com o novo valor.

// 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 para Adaptive Cards, consulte Action.Execute. Para obter orientação sobre como projetar interação para widgets, consulte Diretrizes de design de interação de widgets

OnWidgetContextChanged

Na versão atual, OnWidgetContextChanged só é chamado quando o utilizador altera o tamanho de um widget fixo. Você pode optar por retornar um modelo/dado JSON diferente para o host do widget, dependendo do tamanho solicitado. Você também pode projetar o modelo JSON para suportar todos os tamanhos disponíveis usando renderização condicional com base no valor de host.widgetSize. Se você não precisar enviar um novo modelo ou dados para levar em conta a alteração de tamanho, poderá usar o 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);
    }
}
    

Ativar e Desativar

O método Activate é chamado para notificar o provedor de widgets de que o host do widget está atualmente interessado em receber conteúdo atualizado do provedor. Por exemplo, isso pode significar que o usuário está visualizando ativamente o host do widget. O método Deactivate é chamado para notificar o provedor de widgets 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 está mais interessado em mostrar o conteúdo mais atualizado up-to. Os provedores de widgets 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 de up-todata com preocupações com recursos, como a duração da bateria.

Ativar e Desativar são chamados individualmente para cada widget. Este exemplo monitoriza o estado ativo de cada widget na estrutura auxiliar CompactWidgetInfo. No método Activate, chamamos o UpdateWidget método auxiliar para atualizar nosso widget. Observe que a janela de tempo entre Ativar e Desativar pode ser pequena, pelo que é recomendável tentar fazer com que o percurso do código de atualização do widget seja o mais rápido possível.

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 UpdateWidget método auxiliar para atualizar um widget habilitado. Neste exemplo, verificamos o nome do widget no CompactWidgetInfo struct auxiliar passado para o método e, em seguida, definimos o modelo apropriado e JSON de dados com base em qual widget está sendo atualizado. Um WidgetUpdateRequestOptions é inicializado com o modelo, os dados e o estado personalizado do widget a ser atualizado. Chame WidgetManager::GetDefault para obter uma instância da classe WidgetManager e, em seguida, execute UpdateWidget para enviar os dados do widget atualizado ao anfitrião 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 ativados na inicialização

Quando nosso provedor de widgets é inicializado pela primeira vez, é uma boa ideia perguntar ao WidgetManager se há algum widget em execução que nosso provedor está servindo no momento. Isso ajudará a recuperar o aplicativo para o estado anterior no caso de reiniciar o computador ou o provedor falhar. Chame WidgetManager::GetDefault para obter a instância padrão do gerenciador de widgets para o aplicativo. Em seguida, chame GetWidgetInfos, que retorna uma matriz de objetos WidgetInfo. Copie os IDs, nomes e estado personalizado dos widgets para a estrutura auxiliar CompactWidgetInfo e guarde-os na variável 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;
        }
    }
}

Registre uma fábrica de classes que instanciará o WidgetProvider mediante solicitação

Adicione o cabeçalho que define a classe WidgetProvider às inclusões na parte superior do arquivo do main.cpp seu aplicativo. Também incluiremos mutex aqui.

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

Declare o evento que acionará nosso aplicativo para sair e a função SignalLocalServerShutdown que definirá o evento. Cole o código a seguir no main.cpp.

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

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

Em seguida, terá de criar um CLSID que será usado para identificar o seu fornecedor de widgets para ativação de COM. Gere um GUID no Visual Studio indo para Ferramentas->Criar GUID. Selecione a opção "static const GUID =" e clique em Copiar e, em seguida, cole em main.cpp. Atualize a definição de GUID com a seguinte sintaxe C++/WinRT, definindo o nome da variável GUID widget_provider_clsid. Deixe a versão comentada do GUID porque você precisará desse formato mais tarde, ao empacotar seu 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. Isso é principalmente código clichê que não é específico para implementações de provedor de widget. Observe que CoWaitForMultipleObjects aguarda que nosso evento de desligamento seja acionado antes que o aplicativo seja encerrado.

// 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;
}

Empacote seu aplicativo provedor de widgets

Na versão atual, apenas aplicativos empacotados podem ser registrados como provedores de widgets. As etapas a seguir irão guiá-lo através do processo de empacotamento de seu aplicativo e atualização do manifesto do aplicativo para registrar seu aplicativo no sistema operacional como um provedor de widgets.

Criar um projeto de empacotamento MSIX

NoGerenciador de Soluções , clique com o botão direito do mouse em sua solução e selecione Adicionar-Novo Projeto.... Na caixa de diálogo Adicionar um novo projeto, selecione o modelo "Projeto de empacotamento de aplicativos 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 para a versão 1809 ou posterior e clique em OK. Em seguida, clique com o botão direito do rato no projeto ExampleWidgetProviderPackage e selecione Adicionar referência de projeto>. Selecione o projeto ExampleWidgetProvider e clique em OK.

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

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

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

Observação

Verifique se a versão especificada no elemento PackageReference corresponde à última versão estável que mencionou na etapa anterior.

Se a versão correta do SDK do Aplicativo Windows já estiver instalada no computador e você não quiser agrupar o tempo de execução do SDK em seu pacote, poderá especificar a dependência do pacote no arquivo Package.appxmanifest para o 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 Explorador de Soluções , clique com o botão direito do rato no ficheiro Package.appxmanifest e selecione "Ver Código" para abrir o ficheiro XML do manifesto. Em seguida, você precisa adicionar algumas declarações de namespace para as extensões de pacote de aplicativo que usaremos. Adicione as seguintes definições de namespace ao elemento de nível superior Package.

<!-- 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 novo elemento vazio chamado Extensions. Assegure-se de que isso apareça após a tag de fechamento para uap:VisualElements.

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

    </Extensions>
</Application>

A primeira extensão que precisamos adicionar é a extensão ComServer. Isso registra o ponto de entrada do executável com o sistema operacional. Esta extensão é a aplicação empacotada equivalente ao registo de um servidor COM, através da definição de uma chave de registo, e não é específica para fornecedores de widgets. Adicione o seguinte elemento com:Extension como filho do elemento Extensions. Altere o GUID no atributo Id do elemento com:Class para o GUID gerado num passo 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 widgets. Cole o elemento uap3:Extension no seguinte trecho de código, como filho do elemento Extensions. Certifique-se de substituir 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 para todos esses elementos, consulte Widget provider package manifest XML format.

Adicione ícones e outras imagens ao seu projeto de embalagem

No Explorador de Soluções , clique com o botão direito do mouse no ExampleWidgetProviderPackage e selecione Adicionar ->Nova Pasta. Nomeie essa pasta como ProviderAssets, pois é o que foi usado no Package.appxmanifest da etapa anterior. É aqui que armazenaremos os nossos ícones e capturas de tela para os nossos widgets. Depois de adicionar os ícones e capturas de tela desejados, certifique-se de que os nomes das imagens correspondam ao que vem depois Path=ProviderAssets\ em seu Package.appxmanifest ou os widgets não aparecerão no host do widget.

Para obter informações sobre os requisitos de design para capturas de tela e as convenções de nomenclatura para capturas de tela localizadas, consulte Integrar com o seletor de widgets.

Testando seu provedor de widgets

Certifique-se de ter selecionado a arquitetura que corresponde à sua máquina de desenvolvimento na lista suspensa Solution Platforms, por exemplo, "x64". No Gerenciador de Soluções, clique com o botão direito do mouse em sua solução e selecione Build Solution. Feito isso, clique com o botão direito do mouse no ExampleWidgetProviderPackage e selecione Implantar. Na versão atual, o único host de widget suportado é o Widgets Board. Para ver os widgets, você precisará abrir o Painel de widgets e selecionar Adicionar widgets no canto superior direito. Desloque-se até à parte inferior dos widgets disponíveis e deverás ver o Widget Clima simulado e o Widget de Contagem da Microsoft que foram criados neste tutorial. Clique nos widgets para fixá-los ao seu painel de widgets e testar sua funcionalidade.

Depurando o seu provedor de widgets

Depois de fixar seus widgets, a Plataforma de widgets iniciará seu aplicativo de provedor de widgets 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 widgets em execução ou configurar o Visual Studio para iniciar automaticamente a depuração do processo do provedor de widgets assim que ele for iniciado.

Para se ligar ao processo em execução:

  1. No Visual Studio, clique em Depurar -> Anexar ao processo.
  2. Filtre os processos e encontre o aplicativo de provedor de widgets desejado.
  3. Anexe o depurador.

Para anexar automaticamente o depurador ao processo quando é iniciado:

  1. No Visual Studio, clique em Depurar -> Outros Destinos de Depuração -> Depurar Pacote de Aplicativo Instalado.
  2. Filtre os pacotes e encontre o pacote desejado do provedor de widgets.
  3. Selecione-o e marque a caixa que diz Não iniciar, mas depurar o meu código quando ele estiver iniciado.
  4. Clique em Anexar.

Converter seu 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é Linker -> System e mude o 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, mude int main() para int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

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

Publicando seu widget

Depois de desenvolver e testar seu widget, você pode publicar seu aplicativo na Microsoft Store para que os usuários instalem seus widgets em seus dispositivos. Para obter orientações passo a passo para publicar uma aplicação, consulte Publicar a sua aplicação na Microsoft Store.

A coleção da Widgets Store

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

Captura de ecrã da Microsoft Store a mostrar a coleção de widgets que permite aos utilizadores descobrir aplicações que apresentam Widgets do Windows.

Implementando a personalização de widgets

A partir do Windows App SDK 1.4, os widgets podem oferecer suporte à personalização do usuário. Quando este recurso é implementado, uma opção Personalizar widget é adicionada ao menu de opções 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 de widgets responde às solicitações do host do widget com os modelos JSON visuais e de dados para a experiência regular do widget.
  2. O utilizador clica no botão Personalizar widget no menu de elipse.
  3. O widget levanta o evento OnCustomizationRequested no provedor de widgets para indicar que o utilizador solicitou a experiência de personalização do widget.
  4. O provedor de widgets define um sinalizador interno para indicar que o widget está no modo de personalização. Enquanto estiver no modo de personalização, o provedor de widgets envia os modelos JSON para a interface do usuário de personalização do widget em vez da interface do usuário do widget normal.
  5. Enquanto estiver no modo de personalização, o provedor de widgets recebe eventos de OnActionInvoked à medida que o usuário interage com a interface do usuário de personalização e ajusta sua configuração interna e comportamento com base nas ações do usuário.
  6. Quando a ação associada ao evento OnActionInvoked é a ação "personalização de saída" definida pelo aplicativo, o provedor de widgets redefine seu sinalizador interno para indicar que ele não está mais no modo de personalização e retoma o envio dos modelos JSON visuais e de dados para a experiência regular do widget, refletindo as alterações solicitadas durante a personalização.
  7. O provedor de widgets persiste as opções de personalização no disco ou na nuvem para que as alterações sejam preservadas entre as invocações do provedor de widgets.

Observação

Há um bug conhecido com o Windows Widget Board, para widgets criados usando o SDK de aplicativos 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 serão exibidos no widget ou ajustará a apresentação visual do widget. Para simplificar, o exemplo nesta seção adicionará um 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 de widgets só é suportada no Windows App SDK 1.4 e posterior. Certifique-se de atualizar as referências em seu projeto para a versão mais recente do pacote Nuget.

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

Para que o host do widget saiba que o widget suporta personalização, adicione o atributo IsCustomizable ao eleent Definition 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 à personalização ao widget que foi criado nas etapas anteriores deste artigo, precisaremos atualizar o arquivo de cabeçalho para nosso provedor de widgets, WidgetProvider.h.

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

// 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 callback OnCustomizationRequested da interface IWidgetProvider2.

// WidgetProvider.h

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

Finalmente, 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á ao nosso provedor para retornar ao comportamento normal 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. Este método utiliza o mesmo padrão dos outros callbacks que temos usado. Obtemos o ID para o widget a ser personalizado a partir do WidgetContext e encontramos o CompactWidgetInfo struct auxiliar 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 UpdateWidget método auxiliar que envia nossos dados e modelos JSON 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. Por uma questão de brevidade, o código não relevante para a personalização é omitido neste trecho 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 utilizadores interagem com inputs no nosso modelo de personalização, chama o mesmo manipulador OnActionInvoked como quando o utilizador interage com a experiência de widget regular. Para dar suporte à personalização, procuramos os verbos «reset» e «exitCustomization» no nosso modelo JSON de personalização. Se a ação for para o botão "Redefinir contador", redefiniremos o contador mantido no campo customState de nossa estrutura auxiliar para 0. Se a ação for para o botão "Sair da personalização", definimos o campo inCustomization como false para que, quando chamarmos UpdateWidget, nosso método auxiliar enviará 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 seu widget, verá o botão Personalizar widget no menu de três pontos. Clicar no botão personalizar exibirá seu modelo de personalização.

Uma captura de tela mostrando a interface do usuário 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 normal do widget.