Compartir a través de


Implementación de un proveedor de widgets en una aplicación win32 (C++/WinRT)

En este artículo se explica cómo crear un proveedor de widgets sencillo que implementa la interfaz IWidgetProvider. El host del widget invoca los métodos de esta interfaz para solicitar los datos que definen un widget o para permitir que el proveedor del widget responda a una acción de usuario en un widget. Los proveedores de widgets pueden admitir un único widget o varios widgets. En este ejemplo, definiremos dos widgets diferentes. Uno de los widgets es un widget meteorológico ficticio que ilustra algunas de las opciones de formato que ofrece el marco de tarjetas adaptables. El segundo widget mostrará las acciones del usuario y la característica de estado del widget personalizado manteniendo un contador que se incrementa cada vez que el usuario hace clic en un botón que se muestra en el widget.

Captura de pantalla de un widget meteorológico sencillo. El widget muestra algunos gráficos y datos relacionados con el tiempo, así como texto de diagnóstico que ilustra que se muestra la plantilla para el widget de tamaño medio.

Captura de pantalla de un widget simple de recuento. El widget muestra una cadena que contiene el valor numérico que se va a incrementar y un botón denominado Incrementar, así como un texto de diagnóstico que ilustra que se está mostrando la plantilla para el widget de tamaño pequeño.

Este código de ejemplo de este artículo se adapta del ejemplo de widgets de SDK de Aplicaciones para Windows. Para implementar un proveedor de widgets con C#, consulte Implementación de un proveedor de widgets en una aplicación win32 (C#).

Requisitos previos

  • El dispositivo debe tener habilitado el modo de desarrollador. Para obtener más información, vea Habilitar el dispositivo para el desarrollo.
  • Visual Studio 2022 o posterior con la carga de trabajo Desarrollo de la Plataforma universal de Windows. Asegúrese de agregar el componente para C++ (v143) desde la lista desplegable opcional.

Creación de una aplicación de consola win32 de C++/WinRT

En Visual Studio, cree un nuevo proyecto. En el cuadro de diálogo Crear un nuevo proyecto, establezca el filtro de lenguaje en "C++" y el filtro de plataforma en Windows y, a continuación, seleccione la plantilla de proyecto Aplicación de consola de Windows (C++/WinRT). Ponga al nuevo proyecto el nombre "ExampleWidgetProvider". Cuando se le solicite, establezca la versión de Windows de destino para la aplicación en 1809 o posterior.

Adición de referencias a los paquetes NuGet Biblioteca de implementación de Windows y SDK de Aplicaciones para Windows

En este ejemplo se usa el paquete NuGet SDK de Aplicaciones para Windows estable más reciente. En Explorador de soluciones, haga clic con el botón derecho en Referencias y seleccione Administrar paquetes NuGet.... En el administrador de paquetes NuGet, seleccione la pestaña Examinar y busque "Microsoft.WindowsAppSDK". Seleccione la versión estable más reciente en la lista desplegable Versión y, a continuación, haga clic en Instalar.

En este ejemplo también se usa el paquete NuGet Biblioteca de implementación de Windows. En Explorador de soluciones, haga clic con el botón derecho en Referencias y seleccione Administrar paquetes NuGet.... En el administrador de paquetes NuGet, seleccione la pestaña Examinar y busque "Microsoft.Windows.ImplementationLibrary". Seleccione la versión más reciente en la lista desplegable Versión y, a continuación, haga clic en Instalar.

En el archivo de encabezado precompilado, pch.h, agregue las siguientes directivas de inclusión.

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

Nota

Debe incluir primero el encabezado wil/cppwinrt.h, antes de los encabezados de WinRT.

Para controlar el apagado correcto de la aplicación del proveedor de widgets, necesitamos una implementación personalizada de winrt::get_module_lock. Declaramos previamente el método SignalLocalServerShutdown que se definirá en nuestro archivo main.cpp y establecerá un evento que indicará a la aplicación que debe cerrarse. Agregue el código siguiente al archivo pch.h, justo debajo de la directiva #pragma once, antes de los otros elementos de inclusión.

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

Adición de una clase WidgetProvider para controlar las operaciones del widget

En Visual Studio, haga clic con el botón derecho en el proyecto ExampleWidgetProvider en Explorador de soluciones y seleccione Agregar->clase. En el cuadro de diálogo Agregar clase, asigne a la clase el nombre WidgetProvider y, después, haga clic en Agregar.

Declaración de una clase que implementa la interfaz IWidgetProvider

La interfaz IWidgetProvider define los métodos que invocará el host del widget para iniciar operaciones con el proveedor de widgets. Reemplace la definición de clase vacía en el archivo WidgetProvider.h por el código siguiente. Este código declara una estructura que implementa la interfaz IWidgetProvider y declara prototipos para los métodos de interfaz.

// 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 */

    
};

Además, agregue un método privado , UpdateWidget, que es un método auxiliar que enviará actualizaciones de nuestro proveedor al host del widget.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

Preparación para realizar un seguimiento de los widgets habilitados

Los proveedores de widgets pueden admitir un único widget o varios widgets. Cada vez que el host del widget inicia una operación con el proveedor de widgets, pasa un identificador para identificar el widget asociado a la operación. Cada widget también tiene un nombre asociado y un valor de estado que se puede usar para almacenar datos personalizados. En este ejemplo, declararemos una estructura auxiliar sencilla para almacenar el identificador, el nombre y los datos de cada widget anclado. Los widgets también pueden estar en un estado activo, que se describe en la sección Activar y desactivar a continuación, y realizaremos un seguimiento de este estado para cada widget con un valor booleano. Agregue la siguiente definición al archivo WidgetProvider.h, encima de la declaración de estructura WidgetProvider.

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

Dentro de la declaración WidgetProvider en WidgetProvider.cs, agregue un miembro para el mapa que mantendrá la lista de widgets habilitados, con el identificador de widget como clave 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;

        

Declaración de cadenas JSON de plantilla de widget

En este ejemplo se declararán algunas cadenas estáticas para definir las plantillas JSON para cada widget. Por comodidad, estas plantillas se almacenan en las variables locales declaradas fuera de la definición de clase WidgetProvider. Si necesita un almacenamiento general para las plantillas, se pueden incluir como parte del paquete de aplicación: Acceso a los archivos de paquete. Para obtener información sobre cómo crear el documento JSON de plantilla de widget, consulte Creación de una plantilla de widget con el Diseñador de tarjetas adaptables.

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

Implementación de los métodos IWidgetProvider

En las secciones siguientes, implementaremos los métodos de la interfaz IWidgetProvider. El método auxiliar UpdateWidget al que se llama en varias de estas implementaciones de método se mostrará más adelante en este artículo. Antes de profundizar en los métodos de interfaz, agregue las siguientes líneas a WidgetProvider.cpp, después de las directivas de inclusión, para extraer las API del proveedor de widgets en el espacio de nombres winrt y permitir el acceso al mapa declarado en el paso anterior.

Nota

Solo se garantiza que los objetos pasados a los métodos de devolución de llamada de la interfaz IWidgetProvider sean válidos dentro de la devolución de llamada. No debe almacenar referencias a estos objetos porque su comportamiento fuera del contexto de la devolución de llamada no está definido.

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

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

CreateWidget

El host del widget llama a CreateWidget cuando el usuario ha anclado uno de los widgets de la aplicación en el host del widget. En primer lugar, este método obtiene el identificador y el nombre del widget asociado y agrega una nueva instancia de nuestra estructura auxiliar, CompactWidgetInfo, a la colección de widgets habilitados. A continuación, enviamos la plantilla inicial y los datos del widget, que se encapsulan en el 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

El host del widget llama a DeleteWidget cuando el usuario ha desanclado uno de los widgets de la aplicación del host del widget. Cuando esto ocurra, quitaremos el widget asociado de nuestra lista de widgets habilitados para que no enviemos más actualizaciones para ese widget.

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

OnActionInvoked

El host del widget llama a OnActionInvoked cuando el usuario interactúa con una acción que definió en la plantilla de widget. Para el widget de contador usado en este ejemplo, se declaró una acción con un valor de verbo de "inc" en la plantilla JSON del widget. El código del proveedor de widgets usará este valor de verbo para determinar qué acción se realizará en respuesta a la interacción del usuario.

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

En el método OnActionInvoked, obtenga el valor de verbo comprobando la propiedad Verb del widgetActionInvokedArgs pasado al método. Si el verbo es "inc", sabemos que vamos a incrementar el recuento en el estado personalizado del widget. En WidgetActionInvokedArgs, obtenga el objeto WidgetContext y, a continuación, widgetId para obtener el identificador del widget que se está actualizando. Busque la entrada en nuestro mapa de widgets habilitados con el identificador especificado y, a continuación, actualice el valor de estado personalizado que se usa para almacenar el número de incrementos. Por último, actualice el contenido del widget con el nuevo valor con la función 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 obtener información sobre la sintaxis Action.Execute para Tarjetas adaptables, vea Action.Execute. Para obtener instrucciones sobre cómo diseñar la interacción para widgets, consulte Guía de diseño de interacción de widgets.

OnWidgetContextChanged

En la versión actual, solo se llama a OnWidgetContextChanged cuando el usuario cambia el tamaño de un widget anclado. Puede optar por devolver una plantilla o datos JSON diferentes al host del widget en función del tamaño solicitado. También puede diseñar el JSON de plantilla para admitir todos los tamaños disponibles mediante la representación condicional basada en el valor de host.widgetSize. Si no necesita enviar una nueva plantilla o datos para tener en cuenta el cambio de tamaño, puede utilizar el OnWidgetContextChanged con fines de telemetría.

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

Activar y desactivar

Se llama al método Activar para notificar al proveedor del widget que el host del widget está interesado actualmente en recibir contenido actualizado del proveedor. Por ejemplo, podría significar que el usuario está viendo activamente el host del widget. Se llama al método Desactivar para notificar al proveedor de widgets que el host del widget ya no solicita actualizaciones de contenido. Estos dos métodos definen una ventana en la que el host del widget está más interesado en mostrar el contenido más actualizado. Los proveedores de widgets pueden enviar actualizaciones al widget en cualquier momento, como en respuesta a una notificación de inserción, pero como con cualquier tarea en segundo plano, es importante equilibrar la provisión de contenido actualizado con problemas de recursos como la duración de la batería.

Activar y Desactivar se activan en cada widget. En este ejemplo se realiza un seguimiento del estado activo de cada widget en la estructura auxiliar CompactWidgetInfo. En el método Activar, llamamos al método auxiliar UpdateWidget para actualizar el widget. Tenga en cuenta que el período de tiempo entre Activar y Desactivar puede ser pequeño, por lo que se recomienda intentar que la ruta de acceso del código de actualización del widget sea lo más rápida posible.

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

Actualización de un widget

Defina el método auxiliar UpdateWidget para actualizar un widget habilitado. En este ejemplo, comprobamos el nombre del widget en la estructura auxiliar CompactWidgetInfo que se pasa al método y, a continuación, establecemos la plantilla y el JSON de datos adecuados en función del widget que se esté actualizando. Se inicializa WidgetUpdateRequestOptions con la plantilla, los datos y el estado personalizado del widget que se está actualizando. Llame a WidgetManager::GetDefault para obtener una instancia de la clase WidgetManager y, a continuación, llame a UpdateWidget para enviar los datos del widget actualizado al host del 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);
}

Inicialización de la lista de widgets habilitados en el inicio

Cuando el proveedor de widgets se inicializa por primera vez, es una buena idea preguntar a WidgetManager si hay algún widget en ejecución que nuestro proveedor esté atendiendo actualmente. Ayudará a recuperar la aplicación al estado anterior en caso del reinicio del equipo o bloqueo del proveedor. Llame a WidgetManager::GetDefault para obtener la instancia predeterminada del administrador de widgets para la aplicación. A continuación, llame a GetWidgetInfos, que devuelve una matriz de objetos WidgetInfo. Copie los identificadores, los nombres y el estado personalizado de los widgets en la estructura auxiliar CompactWidgetInfo y guárdelo en la variable de miembro RunningWidgets. Pegue el código siguiente en el constructor de la clase 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;
        }
    }
}

Registro de un generador de clases que creará una instancia de WidgetProvider a petición

Agregue el encabezado que define la clase WidgetProvider a los elementos que se incluyen en la parte superior del archivo main.cpp de la aplicación. También incluiremos exclusión mutua aquí.

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

Declare el evento que desencadenará la salida de la aplicación y la función SignalLocalServerShutdown que establecerá el evento. Pegue el código siguiente en main.cpp.

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

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

A continuación, deberá crear un CLSID que se usará para identificar el proveedor de widgets para la activación COM. Para generar un GUID en Visual Studio, vaya a Herramientas->Crear GUID. Seleccione la opción "static const GUID =" y haga clic en Copiar; a continuación, péguela en main.cpp. Actualice la definición de GUID con la siguiente sintaxis de C++/WinRT, estableciendo el nombre de la variable GUID widget_provider_clsid. Deje la versión comentada del GUID porque necesitará este formato más adelante, al empaquetar la aplicación.

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

Añade la siguiente definición de fábrica de clases a main.cpp. Esto es principalmente código reutilizable que no es específico de las implementaciones del proveedor de widgets. Tenga en cuenta que CoWaitForMultipleObjects espera a que se desencadene el evento de apagado antes de que se cierre la aplicación.

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

Empaquetado de la aplicación del proveedor de widgets

En la versión actual, solo las aplicaciones empaquetadas se pueden registrar como proveedores de widgets. Los pasos siguientes le llevarán a través del proceso de empaquetado de la aplicación y la actualización del manifiesto de aplicación para registrar la aplicación con el sistema operativo como proveedor de widgets.

Creación de un proyecto de empaquetado MSIX

En Explorador de soluciones, haga clic con el botón derecho en la solución y seleccione Agregar->Nuevo proyecto.... En el cuadro de diálogo Agregar un nuevo proyecto, seleccione la plantilla "Proyecto de paquete de aplicación de Windows" y haga clic en Siguiente. Establezca el nombre del proyecto en "ExampleWidgetProviderPackage" y haga clic en Crear. Cuando se le solicite, establezca el destino en la versión 1809 o posterior y haga clic en Aceptar. A continuación, haga clic con el botón derecho en el proyecto ExampleWidgetProviderPackage y seleccione Agregar->Referencia de proyecto. Seleccione el proyecto ExampleWidgetProvider y haga clic en Aceptar.

Adición de la referencia del paquete SDK de Aplicaciones para Windows al proyecto de empaquetado

Debe agregar una referencia al paquete Nuget SDK de Aplicaciones para Windows al proyecto de empaquetado MSIX. En Explorador de soluciones, haga doble clic en el proyecto ExampleWidgetProviderPackage para abrir el archivo ExampleWidgetProviderPackage.wapproj. Agregue el siguiente XML dentro del elemento Proyecto.

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

Nota

Asegúrese de que la versión especificada en el elemento PackageReference coincide con la versión estable más reciente a la que se hizo referencia en el paso anterior.

Si la versión correcta del SDK de Aplicaciones para Windows ya está instalada en el equipo y no desea agrupar el entorno de ejecución del SDK en el paquete, puede especificar la dependencia del paquete en el archivo Package.appxmanifest para el proyecto 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>
...

Actualización del manifiesto del paquete

En el Explorador de soluciones, haga clic con el botón derecho en el archivo Package.appxmanifest y seleccione Ver código para abrir el archivo xml del manifiesto. A continuación, debe agregar algunas declaraciones de espacio de nombres para las extensiones de paquete de la aplicación que usaremos. Agregue las siguientes definiciones de espacio de nombres al elemento de nivel 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 del elemento Application, cree un nuevo elemento vacío denominado Extensions. Asegúrese de que aparece después de la etiqueta de cierre de uap:VisualElements.

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

    </Extensions>
</Application>

La primera extensión que necesitamos agregar es ComServer. Registra el punto de entrada del ejecutable en el sistema operativo. Esta extensión es el equivalente en aplicaciones empaquetadas a registrar un servidor COM estableciendo una clave del Registro, y no es específica de los proveedores de widgets. Agregue el siguiente elemento com:Extension como elemento secundario del elemento Extensions. Cambie el GUID en el atributo Id del elemento com:Class por el GUID que generó en un paso 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>

A continuación, agregue la extensión que registra la aplicación como proveedor de widgets. Pegue el elemento uap3:Extension en el siguiente fragmento de código, como elemento secundario del elemento Extensions. Asegúrese de reemplazar el atributo ClassId del elemento COM por el GUID que usó en los pasos 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 obtener descripciones detalladas e información de formato para todos estos elementos, vea Formato XML del manifiesto de paquete del proveedor de widgets.

Adición de iconos y otras imágenes al proyecto de empaquetado

En Explorador de soluciones, haga clic con el botón derecho en exampleWidgetProviderPackage y seleccione Agregar->Nueva carpeta. Asigne a esta carpeta el nombre ProviderAssets, ya que es lo que se utilizó en Package.appxmanifest del paso anterior. Aquí es donde almacenaremos nuestros iconos y capturas de pantalla para nuestros widgets. Una vez que agregue los iconos y capturas de pantalla deseados, asegúrese de que los nombres de imagen coinciden con lo que viene después de Path=ProviderAssets\ en Package.appxmanifest o los widgets no se mostrarán en el host de widgets.

Para obtener información sobre los requisitos de diseño de las imágenes de las capturas de pantalla y las convenciones de nomenclatura para las capturas de pantalla localizadas, consulte Integración con el selector de widgets.

Prueba del proveedor de widgets

Asegúrese de que ha seleccionado la arquitectura que coincide con la máquina de desarrollo en la lista desplegable Plataformas de soluciones, por ejemplo, "x64". En el Explorador de soluciones, haga clic con el botón secundario en la solución y seleccione Generar solución. Una vez hecho esto, haga clic con el botón derecho en ExampleWidgetProviderPackage y seleccione Implementar. En la versión actual, el único host de widgets admitido es el Panel de widgets. Para ver los widgets, deberá abrir el Panel de widgets y seleccionar Agregar widgets en la parte superior derecha. Desplácese hasta la parte inferior de los widgets disponibles y debería ver el widget meteorológico ficticio y el widget de recuento de Microsoft que se crearon en este tutorial. Haga clic en los widgets para anclarlos al panel de widgets y probar su funcionalidad.

Depuración del proveedor de widgets

Después de anclar los widgets, la plataforma de widgets iniciará la aplicación del proveedor de widgets para recibir y enviar información pertinente sobre el widget. Para depurar el widget en ejecución, puede asociar un depurador a la aplicación del proveedor de widgets en ejecución o configurar Visual Studio para que inicie automáticamente la depuración del proceso del proveedor de widgets una vez iniciado.

Para asocia un elemento al proceso en ejecución:

  1. En Visual Studio, haga clic en Depurar->Asociar al proceso.
  2. Filtre los procesos y busque la aplicación del proveedor de widgets deseada.
  3. Asocie el depurador.

Para asociar automáticamente el depurador al proceso cuando se inicia por primera vez:

  1. En Visual Studio, haga clic en Depurar -> Otros destinos de depuración -> Depurar paquete de aplicación instalado.
  2. Filtre los paquetes y busque el paquete de proveedor de widgets deseado.
  3. Selecciónelo y active la casilla que indica No iniciar, pero depurar mi código cuando se inicie.
  4. Haga clic en Adjuntar.

Conversión de la aplicación de consola en una aplicación de Windows

Para convertir la aplicación de consola creada en este tutorial en una aplicación de Windows:

  1. Haga clic con el botón derecho en el proyecto ExampleWidgetProvider en el Explorador de soluciones y seleccione Propiedades. Vaya a Vinculador -> Sistema y cambie Subsistema de "Consola" a "Windows". Esto también se puede hacer agregando <SubSystem>Windows</SubSystem> a la sección <Link>..</Link> del .vcxproj.
  2. En main.cpp, cambie int main() a int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

Captura de pantalla que muestra las propiedades del proyecto del proveedor de widgets de C++ con el tipo de salida establecido en Aplicación de Windows

Publicación del widget

Después de haber desarrollado y probado el widget, puede publicar la aplicación en Microsoft Store para que los usuarios instalen los widgets en sus dispositivos. Para obtener instrucciones paso a paso para publicar una aplicación, consulta Publicación de la aplicación en Microsoft Store.

Colección de widgets en Store

Una vez publicada la aplicación en Microsoft Store, puede solicitar que la aplicación se incluya en la colección de widgets de Store, que ayuda a los usuarios a descubrir aplicaciones que incorporan widgets de Windows. Para enviar la solicitud, consulte Envío de la información del widget para agregarlo a la colección de Store.

Captura de pantalla de Microsoft Store que muestra la colección de widgets que permite a los usuarios descubrir aplicaciones que incorporan widgets de Windows.

Implementación de la personalización de widgets

A partir del SDK 1.4 de aplicaciones para Windows, los widgets pueden admitir la personalización del usuario. Cuando se implementa esta característica, se agrega una opción Personalizar widget al menú de puntos suspensivos situado encima de la opción Desanclar widget.

Captura de pantalla que muestra un widget con el cuadro de diálogo de personalización mostrado.

En los pasos siguientes, se resume el proceso de personalización del widget.

  1. En un funcionamiento normal, el proveedor de widgets responde a las solicitudes del host del widget con las plantillas JSON de datos y visuales para una experiencia normal del widget.
  2. El usuario hace clic en el botón Personalizar widget en el menú de puntos suspensivos.
  3. El widget genera el evento OnCustomizationRequested en el proveedor de widgets para indicar que el usuario ha solicitado la experiencia de personalización del widget.
  4. El proveedor de widgets establece una marca interna para indicar que el widget está en modo de personalización. Mientras está en modo de personalización, el proveedor de widgets envía las plantillas JSON para la interfaz de usuario de personalización del widget en lugar de la interfaz de usuario normal del widget.
  5. Mientras está en modo de personalización, el proveedor de widgets recibe eventos OnActionInvoked a medida que el usuario interactúe con la interfaz de usuario de personalización y ajuste su configuración interna y comportamiento en función de las acciones del usuario.
  6. Cuando la acción asociada al evento OnActionInvoked sea la acción de "salir de la personalización" definida por la aplicación, el proveedor de widgets restablecerá su marca interna para indicar que ya no está en modo de personalización y se reanudará el envío de las plantillas JSON de datos y visuales para la experiencia normal del widget, lo que reflejará los cambios solicitados durante la personalización.
  7. El proveedor de widgets conserva las opciones de personalización en el disco o en la nube para que los cambios se conserven entre las invocaciones del proveedor de widgets.

Nota:

Hay un error conocido con el Panel de widgets de Windows para los widgets creados mediante el SDK de aplicaciones para Windows que hace que el menú de puntos suspensivos deje de responder después de que se muestre la tarjeta de personalización.

En escenarios típicos de personalización de widgets, el usuario elegirá qué datos se mostrarán en el widget o ajustará la presentación visual del widget. Para una mayor simplicidad, en el ejemplo de esta sección se agregará el comportamiento de personalización que permite al usuario restablecer el contador del widget de recuento implementado en los pasos anteriores.

Nota:

La personalización del widget solo se admite en el SDK 1.4 y versiones posteriores de aplicaciones para Windows. Asegúrese de actualizar las referencias del proyecto a la versión más reciente del paquete Nuget.

Actualice el manifiesto del paquete para declarar la compatibilidad con la personalización

Para que el host del widget sepa que el widget admite la personalización, agregue el atributo IsCustomizable al elemento Definición para el widget y establézcalo en verdadero.

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

Actualizar WidgetProvider.h

Para agregar compatibilidad a la personalización del widget que se creó en los pasos anteriores de este artículo, es necesario actualizar el archivo de encabezado de nuestro proveedor de widgets: WidgetProvider.h.

En primer lugar, actualice la definición CompactWidgetInfo. Esta estructura auxiliar nos ayudará a realizar un seguimiento del estado actual de nuestros widgets activos. Agregue el campo inCustomization, que se usará para realizar el seguimiento cuando el host del widget espere que enviemos nuestra plantilla JSON de personalización en lugar de la plantilla de widget normal.

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

Actualice la declaración WidgetProvider para implementar la interfaz IWidgetProvider2.

// WidgetProvider.h

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

Agregue una declaración para la devolución de llamada OnCustomizationRequested de la interfaz IWidgetProvider2.

// WidgetProvider.h

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

Por último, declare una variable de cadena que defina la plantilla JSON para la interfaz de usuario de personalización del widget. En este ejemplo, tenemos un botón "Restablecer contador" y un botón "Salir de la personalización", que indicarán a nuestro proveedor que vuelva al comportamiento normal del 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"
})";

Actualizar WidgetProvider.cpp

Ahora, actualice el archivo WidgetProvider.cpp para implementar el comportamiento de personalización del widget. Este método usa el mismo patrón que las otras devoluciones de llamada que hemos usado. Obtenemos el identificador para que el widget se personalice desde WidgetContext, busque la estructura auxiliar CompactWidgetInfo asociada a ese widget y establezca el campo inCustomization en verdadero.

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

A continuación, actualizaremos nuestro método auxiliar UpdateWidget, que enviará nuestras plantillas JSON visuales y datos al host del widget. Cuando se actualiza el widget de recuento, se envía la plantilla de widget normal o la de personalización, en función del valor del campo inCustomization. Para una mayor brevedad, el código no relevante para la personalización se omite en este fragmento 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);
}

Cuando los usuarios interactúan con entradas en nuestra plantilla de personalización, se llama al mismo controlador OnActionInvoked que cuando el usuario interactúa con la experiencia de widget normal. Para admitir la personalización, se buscan los verbos "reset" y "exitCustomization" en nuestra plantilla JSON de personalización. Si la acción fuera para el botón "Restablecer contador", se restablecerá el contador mantenido en el campo customState de nuestra estructura auxiliar a 0. Si la acción fuera para el botón "Salir de la personalización", se establecerá el campo inCustomization en falso para que cuando se llame a UpdateWidget, nuestro método auxiliar envíe las plantillas JSON normales y no la plantilla de personalización.

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

Ahora, al implementar el widget, debería ver el botón Personalizar widget en el menú de puntos suspensivos. Al hacer clic en el botón Personalizar, se mostrará la plantilla de personalización.

Captura de pantalla que muestra la interfaz de usuario de personalización de widgets.

Haga clic en el botón Restablecer contador para restablecer el contador a 0. Haga clic en el botón Salir de la personalización para volver al comportamiento normal del widget.