Поделиться через


Реализация поставщика мини-приложений в приложении win32 (C++/WinRT)

В этой статье описывается создание простого поставщика мини-приложений, реализующего интерфейс IWidgetProvider . Методы этого интерфейса вызываются узлом мини-приложения, чтобы запросить данные, определяющие мини-приложение, или разрешить поставщику мини-приложений реагировать на действие пользователя в мини-приложении. Поставщики мини-приложений могут поддерживать одно мини-приложение или несколько мини-приложений. В этом примере мы определим два разных мини-приложения. Одно мини-приложение — это макет мини-приложения погоды, иллюстрирующий некоторые варианты форматирования, предоставляемые платформой адаптивных карточек. Второе мини-приложение будет отображать действия пользователя и функцию пользовательского состояния мини-приложения, сохраняя счетчик, который увеличивается всякий раз, когда пользователь нажимает кнопку, отображаемую на мини-приложении.

Снимок экрана: простое мини-приложение погоды. Мини-приложение показывает некоторые графики, связанные с погодой, а также некоторые диагностические тексты, иллюстрирующие отображение шаблона для мини-приложения среднего размера.

Снимок экрана: простое мини-приложение подсчета. В мини-приложении показана строка, содержащая числовое значение, которое будет увеличиваться, и приращение кнопки, а также некоторые диагностические тексты, иллюстрирующие отображение шаблона для мини-приложения небольшого размера.

Этот пример кода в этой статье адаптирован из примера мини-приложений пакета SDK для Windows. Сведения о реализации поставщика мини-приложений с помощью C#см. в статье "Реализация поставщика мини-приложений" в приложении win32 (C#).

Необходимые компоненты

  • Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Включение устройства для разработки".
  • Visual Studio 2022 или более поздней версии с рабочей нагрузкой разработки универсальная платформа Windows. Обязательно добавьте компонент для C++ (версии 143) из дополнительного раскрывающегося списка.

Создание консольного приложения C++/WinRT win32

В Visual Studio создайте проект . В диалоговом окне "Создание проекта" установите для фильтра языка значение "C++" и фильтр платформы в Windows, а затем выберите шаблон проекта консольного приложения Windows (C++/WinRT). Присвойте новому проекту имя ExampleWidgetProvider. При появлении запроса задайте целевую версию Windows для приложения версии 1809 или более поздней.

Добавление ссылок на пакеты NuGet пакета SDK для приложений Windows и библиотеки реализации Windows

В этом примере используется последний стабильный пакет Пакета NuGet для приложений Windows. В Обозреватель решений щелкните правой кнопкой мыши ссылки и выберите пункт "Управление пакетами NuGet...". В диспетчере пакетов NuGet перейдите на вкладку "Обзор" и найдите "Microsoft.WindowsAppSDK". Выберите последнюю стабильную версию в раскрывающемся списке "Версия" , а затем нажмите кнопку "Установить".

В этом примере также используется пакет NuGet библиотеки реализации Windows. В Обозреватель решений щелкните правой кнопкой мыши ссылки и выберите пункт "Управление пакетами NuGet...". В диспетчере пакетов NuGet перейдите на вкладку "Обзор" и найдите "Microsoft.Windows.ImplementationLibrary". Выберите последнюю версию в раскрывающемся списке "Версия" и нажмите кнопку "Установить".

В предварительно скомпилированного файла заголовка pch.h добавьте следующие директивы include.

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

Примечание.

Перед любыми заголовками WinRT необходимо сначала включить заголовок wil/cppwinrt.h.

Чтобы правильно завершить работу приложения поставщика мини-приложений, необходимо настроить пользовательскую реализацию winrt::get_module_lock. Мы предварительно объявляем метод SignalLocalServerShutdown , который будет определен в нашем main.cpp файле и установим событие, которое сигнализирует приложению о выходе. Добавьте следующий код в файл pch.h, непосредственно под директивой #pragma once , прежде чем другие включают.

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

Добавление класса WidgetProvider для обработки операций мини-приложения

В Visual Studio щелкните проект правой ExampleWidgetProvider кнопкой мыши в Обозреватель решений и выберите "Класс надстройки>". В диалоговом окне "Добавить класс" назовите класс "WidgetProvider" и нажмите кнопку "Добавить".

Объявление класса, реализующего интерфейс IWidgetProvider

Интерфейс IWidgetProvider определяет методы, которые узел мини-приложения вызывает для запуска операций с поставщиком мини-приложений. Замените пустое определение класса в файле WidgetProvider.h следующим кодом. Этот код объявляет структуру, реализующую интерфейс IWidgetProvider , и объявляет прототипы для методов интерфейса.

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

    
};

Кроме того, добавьте частный метод UpdateWidget, который является вспомогательным методом, который будет отправлять обновления от нашего поставщика в узел мини-приложения.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

Подготовка к отслеживанию включенных мини-приложений

Поставщик мини-приложений может поддерживать одно мини-приложение или несколько мини-приложений. Каждый раз, когда узел мини-приложения инициирует операцию с поставщиком мини-приложений, он передает идентификатор для идентификации мини-приложения, связанного с операцией. Каждый мини-приложение также имеет связанное имя и значение состояния, которое можно использовать для хранения пользовательских данных. В этом примере мы объявим простую вспомогательный структуру для хранения идентификатора, имени и данных для каждого закрепленного мини-приложения. Мини-приложения также могут находиться в активном состоянии, которое рассматривается в разделе "Активация и деактивация " ниже, и мы отслеживаем это состояние для каждого мини-приложения с логическим значением. Добавьте следующее определение в файл WidgetProvider.h над объявлением структуры WidgetProvider .

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

В объявлении WidgetProvider в WidgetProvider.h добавьте элемент для карты, который будет поддерживать список включенных мини-приложений, используя идентификатор мини-приложения в качестве ключа для каждой записи.

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

        

Объявление строк JSON шаблона мини-приложения

В этом примере будут объявлены некоторые статические строки для определения шаблонов JSON для каждого мини-приложения. Для удобства эти шаблоны хранятся в локальных переменных, объявленных за пределами определения класса WidgetProvider . Если вам нужен общий объем хранилища для шаблонов, их можно включить в состав пакета приложения: доступ к файлам пакетов. Сведения о создании документа JSON шаблона мини-приложения см. в статье "Создание шаблона мини-приложения с помощью конструктора адаптивных карточек".

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

Реализация методов IWidgetProvider

В следующих нескольких разделах мы реализуем методы интерфейса IWidgetProvider . Вспомогательный метод UpdateWidget , который вызывается в нескольких из этих реализаций методов, будет показан далее в этой статье. Перед погружением в методы интерфейса добавьте следующие строки в после WidgetProvider.cppдиректив include, чтобы извлечь API-интерфейсы поставщика мини-приложений в пространство имен winrt и разрешить доступ к карте, объявленной на предыдущем шаге.

Примечание.

Объекты, передаваемые в методы обратного вызова интерфейса IWidgetProvider , гарантированно допустимы только в пределах обратного вызова. Не следует хранить ссылки на эти объекты, так как их поведение вне контекста обратного вызова не определено.

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

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

CreateWidget

Узел мини-приложения вызывает CreateWidget , когда пользователь закрепляет одно из мини-приложений в узле мини-приложения. Сначала этот метод получает идентификатор и имя связанного мини-приложения и добавляет новый экземпляр вспомогательной структуры CompactWidgetInfo в коллекцию включенных мини-приложений. Затем мы отправим исходный шаблон и данные для мини-приложения, который инкапсулируется в вспомогательный метод 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

Узел мини-приложения вызывает DeleteWidget , когда пользователь открепил одно из мини-приложений из узла мини-приложения. Когда это происходит, мы удалим связанное мини-приложение из списка включенных мини-приложений, чтобы мы не отправляли дальнейшие обновления для этого мини-приложения.

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

OnActionInvoked

Узел мини-приложения вызывает OnActionInvoked , когда пользователь взаимодействует с действием, определенным в шаблоне мини-приложения. Для мини-приложения счетчика, используемого в этом примере, было объявлено действие со значением команды inc в шаблоне JSON для мини-приложения. Код поставщика мини-приложений будет использовать это значение команды , чтобы определить, какие действия следует предпринять в ответ на взаимодействие пользователя.

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

В методе OnActionInvoked получите значение команды, проверка свойству Verb объекта WidgetActionInvokedArgs, переданного в метод. Если команда имеет значение "inc", то мы знаем, что мы собираемся увеличить количество в пользовательском состоянии для мини-приложения. Из мини-приложения WidgetActionInvokedArgs получите объект WidgetContext, а затем мини-код, чтобы получить идентификатор обновляемого мини-приложения. Найдите запись в наших включенных мини-приложениях с указанным идентификатором, а затем обновите настраиваемое значение состояния, которое используется для хранения числа добавок. Наконец, обновите содержимое мини-приложения новым значением с помощью вспомогательной функции 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);
        }
    }
}

Сведения о синтаксисе Action.Execute для адаптивных карточек см. в разделе Action.Execute. Рекомендации по проектированию взаимодействия с мини-приложениями см . в руководстве по проектированию взаимодействия с мини-приложениями

OnWidgetContextChanged

В текущем выпуске OnWidgetContextChanged вызывается только при изменении размера закрепленного мини-приложения. Вы можете вернуть другой шаблон ИЛИ данные JSON в узел мини-приложения в зависимости от размера, который запрашивается. Вы также можете разработать json шаблона для поддержки всех доступных размеров с помощью условной отрисовки на основе значения host.widgetSize. Если вам не нужно отправлять новый шаблон или данные для учета изменения размера, можно использовать OnWidgetContextChanged для целей телеметрии.

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

        UpdateWidget(localWidgetInfo);
    }
}
    

Активация и деактивация

Метод Activate вызывается для уведомления поставщика мини-приложений о том, что узел мини-приложения в настоящее время заинтересован в получении обновленного содержимого от поставщика. Например, это может означать, что пользователь в настоящее время активно просматривает узел мини-приложения. Метод деактивации вызывается для уведомления поставщика мини-приложений о том, что узел мини-приложения больше не запрашивает обновления содержимого. Эти два метода определяют окно, в котором узел мини-приложения наиболее заинтересован в отображении самого актуального содержимого. Поставщики мини-приложений могут отправлять обновления в мини-приложение в любое время, например в ответ на push-уведомление, но как и в любой фоновой задаче, важно сбалансировать содержимое, предоставляющее актуальное содержимое с проблемами ресурсов, такими как время работы батареи.

Активация и деактивация вызываются на основе каждого мини-приложения. В этом примере отслеживается активное состояние каждого мини-приложения в вспомогательной структуре CompactWidgetInfo . В методе Activate мы вызываем вспомогательный метод UpdateWidget для обновления мини-приложения. Обратите внимание, что интервал времени между активацией и деактивацией может быть небольшим, поэтому рекомендуется сделать путь обновления мини-приложения как можно быстрее.

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

Обновление мини-приложения

Определите вспомогательный метод UpdateWidget для обновления включенного мини-приложения. В этом примере мы проверка имя мини-приложения в вспомогательной структуре CompactWidgetInfo, переданной в метод, а затем задайте соответствующий шаблон и JSON данных на основе того, на каком мини-приложении обновляется. Мини-приложениеUpdateRequestOptions инициализируется шаблоном, данными и пользовательским состоянием для обновляемого мини-приложения. Вызовите WidgetManager::GetDefault, чтобы получить экземпляр класса WidgetManager, а затем вызовите UpdateWidget, чтобы отправить обновленные данные мини-приложения на узел мини-приложения.

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

Инициализация списка включенных мини-приложений при запуске

Когда наш поставщик мини-приложений впервые инициализирован, рекомендуется попросить WidgetManager , если есть какие-либо запущенные мини-приложения, которые в настоящее время обслуживает наш поставщик. Это поможет восстановить приложение до предыдущего состояния в случае перезагрузки компьютера или сбоя поставщика. Вызовите WidgetManager::GetDefault , чтобы получить экземпляр диспетчера мини-приложений по умолчанию. Затем вызовите GetWidgetInfos, который возвращает массив объектов WidgetInfo. Скопируйте идентификаторы мини-приложений, имена и пользовательское состояние в вспомогательные структуры CompactWidgetInfo и сохраните его в переменную члена RunningWidgets . Вставьте следующий код в конструктор для класса 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;
        }
    }
}

Регистрация фабрики классов, которая создаст экземпляр WidgetProvider по запросу

Добавьте заголовок, определяющий класс WidgetProvider , в список в верхней части файла приложения main.cpp . Мы также будем включать мьютекс здесь.

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

Объявите событие, которое активирует наше приложение для выхода и функцию SignalLocalServerShutdown , которая установит событие. Вставьте следующий код в main.cpp.

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

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

Затем необходимо создать CLSID , который будет использоваться для идентификации поставщика мини-приложений для активации COM. Создайте GUID в Visual Studio, перейдя в раздел "Сервис-создание> GUID". Выберите параметр "статический guid констант =" и нажмите кнопку "Копировать ", а затем вставьте его main.cpp. Обновите определение GUID следующим синтаксисом C++/WinRT, задав имя переменной GUID widget_provider_clsid. Оставьте закомментированную версию GUID, так как вам потребуется этот формат позже при упаковке приложения.

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

Добавьте следующее определение main.cppфабрики классов. В основном это стандартный код, который не зависит от реализаций поставщика мини-приложений. Обратите внимание, что CoWaitForMultipleObjects ожидает активации события завершения работы до выхода приложения.

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

Упаковка приложения поставщика мини-приложений

В текущем выпуске только упакованные приложения можно зарегистрировать в качестве поставщиков мини-приложений. Ниже описано, как упаковать приложение и обновить манифест приложения, чтобы зарегистрировать приложение в качестве поставщика мини-приложений.

Создание проекта упаковки MSIX

В Обозреватель решений щелкните решение правой кнопкой мыши и выберите "Добавить> новый проект...". В диалоговом окне "Добавление нового проекта" выберите шаблон "Проект упаковки приложений Windows" и нажмите кнопку "Далее". Задайте для имени проекта значение ExampleWidgetProviderPackage и нажмите кнопку "Создать". При появлении запроса задайте целевую версию версии 1809 или более поздней и нажмите кнопку "ОК". Затем щелкните правой кнопкой мыши проект ExampleWidgetProviderPackage и выберите ссылку add-Project>. Выберите проект ExampleWidgetProvider и нажмите кнопку "ОК".

Добавление ссылки на пакет sdk для приложений Windows в проект упаковки

Необходимо добавить ссылку на пакет nuget пакета sdk для приложений Windows в проект упаковки MSIX. В Обозреватель решений дважды щелкните проект ExampleWidgetProviderPackage, чтобы открыть файл ExampleWidgetProviderPackage.wapproj. Добавьте следующий xml-код в элемент Project .

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

Примечание.

Убедитесь, что версия , указанная в элементе PackageReference , соответствует последней стабильной версии, на которая ссылается на предыдущий шаг.

Если на компьютере уже установлена правильная версия пакета SDK для приложений Windows, и вы не хотите упаковывать среду выполнения пакета в пакет, можно указать зависимость пакета в файле Package.appxmanifest для проекта 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>
...

Обновление манифеста пакета

В Обозреватель решений щелкните файл правой кнопкой мыши Package.appxmanifest и выберите команду Просмотреть код, чтобы открыть XML-файл манифеста. Затем необходимо добавить некоторые объявления пространства имен для расширений пакета приложения, которые мы будем использовать. Добавьте следующие определения пространства имен в элемент 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"

В элементе Application создайте пустой элемент с именем Extensions. Убедитесь, что это происходит после закрывающего тега uap :VisualElements.

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

    </Extensions>
</Application>

Первым расширением , который необходимо добавить, является расширение ComServer . Это регистрирует точку входа исполняемого файла в ОС. Это расширение является упаковаемым приложением, эквивалентным регистрации COM-сервера, задав раздел реестра и не относясь к поставщикам мини-приложений. Добавьте следующий элемент com:Extension в качестве дочернего элемента Extensions . Измените GUID в атрибуте идентификатора элемента com:Class на GUID, созданный на предыдущем шаге.

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

Затем добавьте расширение, которое регистрирует приложение в качестве поставщика мини-приложений. Вставьте элемент uap3:Extension в следующий фрагмент кода в качестве дочернего элемента Extensions. Обязательно замените атрибут ClassId элемента COM идентификатором GUID, который использовался на предыдущих шагах.

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

Подробные описания и сведения о форматировании всех этих элементов см . в формате XML манифеста пакета поставщика мини-приложений.

Добавление значков и других изображений в проект упаковки

В Обозреватель решений щелкните правой кнопкой мыши объект ExampleWidgetProviderPackage и выберите "Добавить новую> папку". Назовите эту папку ProviderAssets так, как это было использовано на Package.appxmanifest предыдущем шаге. Здесь мы будем хранить значки и снимки экрана для мини-приложений. После добавления нужных значков и снимка экрана убедитесь, что имена изображений совпадают с тем, что происходит после Path=ProviderAssets\ в приложении Package.appxmanifest или мини-приложения не будут отображаться в узле мини-приложения.

Сведения о требованиях к проектированию изображений снимок экрана и соглашения об именовании локализованных снимков см. в разделе "Интеграция с средство выбора мини-приложений".

Тестирование поставщика мини-приложений

Убедитесь, что вы выбрали архитектуру, соответствующую компьютеру разработки, в раскрывающемся списке "Платформы решений", например "x64". В Обозреватель решений щелкните решение правой кнопкой мыши и выберите команду "Создать решение". После этого щелкните правой кнопкой мыши имя ExampleWidgetProviderPackage и выберите "Развернуть". В текущем выпуске единственным поддерживаемым узлом мини-приложений является Доска мини-приложений. Чтобы просмотреть мини-приложения, необходимо открыть доску мини-приложений и выбрать пункт "Добавить мини-приложения " в правом верхнем углу. Прокрутите страницу до нижней части доступных мини-приложений, и вы увидите макет мини-приложения погоды и мини-приложение подсчета Майкрософт, созданные в этом руководстве. Щелкните мини-приложения, чтобы закрепить их на доске мини-приложений и проверить их функциональные возможности.

Отладка поставщика мини-приложений

После закрепления мини-приложений платформа мини-приложений запустит приложение поставщика мини-приложений для получения и отправки соответствующих сведений о мини-приложении. Чтобы выполнить отладку запущенного мини-приложения, можно подключить отладчик к приложению поставщика мини-приложений или настроить Visual Studio для автоматической отладки процесса поставщика мини-приложений после его запуска.

Чтобы подключиться к выполняемой процедуре, выполните следующие действия:

  1. В Visual Studio щелкните "Отладка—> присоединение к процессу".
  2. Отфильтруйте процессы и найдите нужное приложение поставщика мини-приложений.
  3. Подключение отладчика.

Чтобы автоматически подключить отладчик к процессу при первоначальном запуске:

  1. В Visual Studio щелкните "Отладка "> Другие целевые объекты отладки —> отладка установленного пакета приложения".
  2. Отфильтруйте пакеты и найдите нужный пакет поставщика мини-приложений.
  3. Выберите его и проверка поле, которое говорит, что не запускается, но отладка кода при запуске.
  4. Нажмите кнопку Присоединить.

Преобразование консольного приложения в приложение Windows

Чтобы преобразовать консольное приложение, созданное в этом пошаговом руководстве, в приложение Windows:

  1. Щелкните правой кнопкой мыши проект ExampleWidgetProvider в Обозреватель решений и выберите "Свойства". Перейдите к компоновщику —> система и измените подсистему с консоли на Windows. Это также можно сделать, добавив <подсистему>Windows</SubSystem> в <Link>..</Link> раздела .vcxproj.
  2. В main.cpp перейдите int main() на int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

Снимок экрана: свойства проекта поставщика мини-приложений C++ с типом вывода, заданным для приложения Windows

Публикация мини-приложения

После разработки и тестирования мини-приложения вы можете опубликовать приложение в Microsoft Store, чтобы пользователи могли устанавливать мини-приложения на своих устройствах. Пошаговые инструкции по публикации приложения см. в статье "Публикация приложения в Microsoft Store".

Коллекция магазинов мини-приложений

После публикации приложения в Microsoft Store вы можете запросить включение приложения в коллекцию магазинов мини-приложений, которая помогает пользователям обнаруживать приложения с мини-приложениями, включающими мини-приложения Windows. Чтобы отправить запрос, см. сведения о отправке сведений о мини-приложении для добавления в коллекцию Магазинов.

Снимок экрана: Microsoft Store с коллекцией мини-приложений, которая позволяет пользователям обнаруживать приложения с мини-приложениями Windows.

Реализация настройки мини-приложения

Начиная с пакета SDK для приложений Windows 1.4 мини-приложения могут поддерживать настройку пользователей. При реализации этой функции параметр мини-приложения "Настройка" добавляется в меню с многоточием над параметром "Открепить мини-приложение ".

Снимок экрана: мини-приложение с отображаемым диалогом настройки.

Ниже приведены инструкции по настройке мини-приложений.

  1. В обычной работе поставщик мини-приложений отвечает на запросы узла мини-приложения с помощью шаблонов визуального элемента и данных JSON для обычного мини-приложения.
  2. Пользователь нажимает кнопку "Настроить мини-приложение" в меню с многоточием.
  3. Мини-приложение вызывает событие OnCustomizationRequested в поставщике мини-приложений, чтобы указать, что пользователь запрашивал возможность настройки мини-приложения.
  4. Поставщик мини-приложений задает внутренний флаг, указывающий, что мини-приложение находится в режиме настройки. В режиме настройки поставщик мини-приложений отправляет шаблоны JSON для пользовательского интерфейса настройки мини-приложения вместо обычного пользовательского интерфейса мини-приложения.
  5. В режиме настройки поставщик мини-приложения получает события OnActionInvoked , так как пользователь взаимодействует с пользовательским интерфейсом настройки и настраивает внутреннюю конфигурацию и поведение на основе действий пользователя.
  6. Если действие, связанное с событием OnActionInvoked , является действием, определяемым приложением , поставщик мини-приложения сбрасывает внутренний флаг, чтобы указать, что он больше не находится в режиме настройки и возобновляет отправку шаблонов JSON визуального элемента и данных для обычного мини-приложения, отражая изменения, запрошенные во время настройки.
  7. Поставщик мини-приложений сохраняет параметры настройки на диске или в облаке, чтобы изменения сохранялись между вызовами поставщика мини-приложений.

Примечание.

Существует известная ошибка с доской мини-приложений Windows, для мини-приложений, созданных с помощью пакета SDK для приложений Windows, что приводит к тому, что меню с многоточием перестает отвечать после отображения карта настройки.

В типичных сценариях настройки мини-приложения пользователь выбирает, какие данные отображаются в мини-приложении или настраивает визуальную презентацию мини-приложения. Для простоты в этом разделе будет добавлено поведение настройки, позволяющее пользователю сбросить счетчик мини-приложения подсчета, реализованного на предыдущих шагах.

Примечание.

Настройка мини-приложения поддерживается только в пакете SDK для приложений Windows 1.4 и более поздних версий. Обязательно обновите ссылки в проекте до последней версии пакета Nuget.

Обновление манифеста пакета для объявления поддержки настройки

Чтобы сообщить узлу мини-приложения, что мини-приложение поддерживает настройку, добавьте атрибут IsCustomizable в определение для мини-приложения и задайте для него значение true.

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

Обновление WidgetProvider.h

Чтобы добавить поддержку настройки в мини-приложение, созданное в предыдущих шагах этой статьи, необходимо обновить файл заголовка для нашего поставщика мини-приложений, WidgetProvider.h.

Сначала обновите определение CompactWidgetInfo . Эта вспомогательные структуры помогают отслеживать текущее состояние наших активных мини-приложений. Добавьте поле inCustomization, которое будет использоваться для отслеживания того, когда узел мини-приложения ожидает отправки шаблона json настройки, а не обычного шаблона мини-приложения.

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

Обновите объявление WidgetProvider, чтобы реализовать интерфейс IWidgetProvider2.

// WidgetProvider.h

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

Добавьте объявление для обратного вызова OnCustomizationRequested интерфейса IWidgetProvider2.

// WidgetProvider.h

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

Наконец, объявите строковую переменную, определяющую шаблон JSON для пользовательского интерфейса настройки мини-приложения. В этом примере у нас есть кнопка "Сброс счетчика" и кнопка "Выйти из настройки", которая сигнализирует нашему поставщику вернуться к обычному поведению мини-приложений.

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

Обновление WidgetProvider.cpp

Теперь обновите файл WidgetProvider.cpp, чтобы реализовать поведение настройки мини-приложения. Этот метод использует тот же шаблон, что и другие обратные вызовы, которые мы использовали. Мы получаем идентификатор мини-приложения для настройки из WidgetContext и найдите вспомогательные структуры CompactWidgetInfo , связанные с этим мини-приложением, и задайте для поля inCustomization значение 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);
    }
}

Затем мы обновим вспомогательный метод UpdateWidget , который отправляет наши данные и шаблоны visual JSON на узел мини-приложения. При обновлении мини-приложения подсчета мы отправим обычный шаблон мини-приложения или шаблон настройки в зависимости от значения поля inCustomization . Для краткости код, который не относится к настройке, опущен в этом фрагменте кода.

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

Когда пользователи взаимодействуют с входными данными в шаблоне настройки, он вызывает тот же обработчик OnActionInvoked , что и при взаимодействии пользователя с обычным интерфейсом мини-приложения. Для поддержки настройки мы ищем команды "сброс" и "exitCustomization" из шаблона JSON настройки. Если действие предназначено для кнопки "Сброс счетчика", мы сбросим счетчик, удерживаемый в поле customState вспомогательной структуры, значение 0. Если действие предназначено для кнопки "Выход из настройки", мы установите для поля inCustomization значение false, чтобы при вызове UpdateWidget наш вспомогательный метод отправлял обычные шаблоны JSON, а не шаблон настройки.

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

Теперь при развертывании мини-приложения вы увидите кнопку "Настроить мини-приложение " в меню многоточия. При нажатии кнопки настройки отобразится шаблон настройки.

Снимок экрана: пользовательский интерфейс настройки мини-приложений.

Нажмите кнопку "Сброс счетчика", чтобы сбросить счетчик до 0. Нажмите кнопку "Выйти", чтобы вернуться к обычному поведению мини-приложения.