Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описывается создание простого поставщика мини-приложений, реализующего интерфейс IWidgetProvider . Методы этого интерфейса вызываются узлом мини-приложения, чтобы запросить данные, определяющие мини-приложение, или разрешить поставщику мини-приложений реагировать на действие пользователя в мини-приложении. Поставщики мини-приложений могут поддерживать одно мини-приложение или несколько мини-приложений. В этом примере мы определим два разных мини-приложения. Одним из виджетов является макет погодного виджета, иллюстрирующий некоторые варианты форматирования, предоставляемые платформой Adaptive Cards. Второе мини-приложение будет отображать действия пользователя и функцию пользовательского состояния мини-приложения, сохраняя счетчик, который увеличивается всякий раз, когда пользователь нажимает кнопку, отображаемую на мини-приложении.
Этот пример кода в этой статье адаптирован из примера мини-приложений пакета SDK для Windows. Сведения о реализации поставщика мини-приложений с помощью C#см. в статье "Реализация поставщика мини-приложений" в приложении win32 (C#).
Требования
- Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Параметры" для разработчиков.
- Visual Studio 2026 или более поздней версии с рабочей нагрузкой разработки приложений WinUI . Обязательно добавьте компонент для C++ (версии 143) из дополнительного раскрывающегося списка.
Создание консольного приложения C++/WinRT win32
В Visual Studio создайте проект . В диалоговом окне "Создание проекта" установите для фильтра языка значение "C++" и фильтр платформы в Windows, а затем выберите шаблон проекта консольного приложения Windows (C++/WinRT). Присвойте новому проекту имя ExampleWidgetProvider. При появлении запроса задайте целевую версию Windows для приложения версии 1809 или более поздней.
Добавьте ссылки на пакеты NuGet Windows App SDK и Windows Implementation Library
В этом примере используется последний стабильный пакет Пакета 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 щелкните правой кнопкой мыши по проекту
Объявление класса, реализующего интерфейс 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
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
private:
...
static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
Объявите строки JSON шаблона виджета
В этом примере будут объявлены некоторые статические строки для создания шаблонов JSON для каждого виджета. Для удобства эти шаблоны хранятся в локальных переменных, объявленных за пределами определения класса WidgetProvider . Если вам нужно общее хранилище для шаблонов, их можно включить в состав пакета приложения: Доступ к файлам пакетов. Сведения о создании документа JSON шаблона мини-приложения см. в статье "Создание шаблона мини-приложения с помощью конструктора адаптивных карточек".
В последнем выпуске приложения, реализующие мини-приложения Windows, могут настраивать заголовок, отображаемый для их мини-приложения в доске мини-приложений, переопределяя презентацию по умолчанию. Для получения дополнительной информации см. в разделе Настройка области заголовка виджета.
Примечание.
В последней версии приложения, реализующие виджеты Windows, могут заполнять содержимое виджета с помощью HTML, передаваемого по указанному URL-адресу, вместо предоставления содержимого в формате схемы Adaptive Card в полезных данных JSON, передаваемых от поставщика в доску виджетов. Поставщики мини-приложений по-прежнему должны предоставлять нагрузку 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 когда пользователь закрепляет одно из ваших мини-приложений в нем. Сначала этот метод получает идентификатор и имя связанного мини-приложения и добавляет новый экземпляр вспомогательной структуры 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, когда пользователь открепляет один из ваших виджетов от хоста виджетов. Когда это происходит, мы удалим связанное мини-приложение из списка включенных мини-приложений, чтобы мы не отправляли дальнейшие обновления для этого мини-приложения.
// 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, а затем WidgetId, чтобы получить ID обновляемого мини-приложения. Найдите запись в карте наших активированных виджетов с указанным идентификатором, а затем обновите пользовательское значение состояния, используемое для хранения количества увеличений. Наконец, обновите содержимое мини-приложения новым значением с помощью вспомогательной функции 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 на основе того, какое мини-приложение обновляется. WidgetUpdateRequestOptions инициализируется с использованием шаблона, данных и кастомного состояния для обновляемого виджета. Вызовите 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". Выберите опцию "static const 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 reference. Выберите проект ExampleWidgetProvider и нажмите кнопку "ОК".
Добавление ссылки на пакет sdk для приложений Windows в проект упаковки
Необходимо добавить ссылку на пакет NuGet Windows App SDK в проект упаковки 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 в атрибуте Id элемента 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 для автоматической отладки процесса поставщика мини-приложений после его запуска.
Чтобы подключиться к выполняемой процедуре, выполните следующие действия:
- В Visual Studio щелкните "Отладка—> присоединение к процессу".
- Отфильтруйте процессы и найдите нужное приложение поставщика мини-приложений.
- Подключение отладчика.
Чтобы автоматически подключить отладчик к процессу при первоначальном запуске:
- В Visual Studio щелкните "Отладка "> Другие целевые объекты отладки —> отладка установленного пакета приложения".
- Отфильтруйте пакеты и найдите нужный пакет поставщика мини-приложений.
- Выберите его и установите флажок напротив пункта "Не запускать, но отладка кода при его запуске".
- Нажмите кнопку Присоединить.
Преобразование консольного приложения в приложение Windows
Чтобы преобразовать консольное приложение, созданное в этом пошаговом руководстве, в приложение Windows:
- Щелкните правой кнопкой мыши проект ExampleWidgetProvider в Обозреватель решений и выберите "Свойства". Перейдите к Linker> Система и измените SubSystem с «Console» на «Windows». Это также можно сделать, добавив <SubSystem>Windows</SubSystem> в секцию <Link>..</Link> файла .vcxproj.
- В main.cpp перейдите
int main()наint WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).
Публикация виджета
После разработки и тестирования мини-приложения вы можете опубликовать приложение в Microsoft Store, чтобы пользователи могли устанавливать мини-приложения на своих устройствах. Пошаговые инструкции по публикации приложения см. в статье "Публикация приложения в Microsoft Store".
Коллекция магазинов мини-приложений
После публикации приложения в Microsoft Store вы можете запросить включение приложения в коллекцию магазинов мини-приложений, которая помогает пользователям обнаруживать приложения с мини-приложениями, включающими мини-приложения Windows. Чтобы отправить запрос, см. Отправка информации о вашем мини-приложении для добавления в коллекцию Магазина.
Настройка виджета
Начиная с пакета SDK для приложений Windows 1.4 мини-приложения могут поддерживать настройку пользователей. При реализации этой функции опция Настройка мини-приложения добавляется в меню с троеточием над опцией Открепить мини-приложение.
Ниже приведены инструкции по настройке мини-приложений.
- Во время нормальной работы поставщик виджетов отвечает на запросы от узла виджетов, предоставляя визуальные и данные JSON-шаблоны для стандартного пользовательского опыта работы с виджетами.
- Пользователь нажимает кнопку "Настроить виджет" в меню с троеточием.
- Мини-приложение вызывает событие OnCustomizationRequested в поставщике мини-приложений, чтобы указать, что пользователь запрашивал возможность настройки мини-приложения.
- Поставщик мини-приложений задает внутренний флаг, указывающий, что мини-приложение находится в режиме настройки. В режиме настройки поставщик мини-приложений отправляет шаблоны JSON для пользовательского интерфейса настройки мини-приложения вместо обычного пользовательского интерфейса мини-приложения.
- В режиме настройки поставщик мини-приложения получает события OnActionInvoked, когда пользователь взаимодействует с пользовательским интерфейсом настройки и регулирует внутреннюю конфигурацию и поведение на основе действий пользователя.
- Если действие, связанное с событием OnActionInvoked, является действием «выход из настройки», определяемым приложением, поставщик мини-приложения сбрасывает внутренний флаг, чтобы указать, что оно больше не находится в режиме настройки и возобновляет отправку визуальных и данных JSON шаблонов для обычного опыта мини-приложения, отражая изменения, запрошенные во время настройки.
- Поставщик мини-приложений сохраняет параметры настройки на диске или в облаке, чтобы изменения сохранялись между вызовами поставщика мини-приложений.
Примечание.
Существует известная ошибка с панелью виджетов Windows для виджетов, созданных с помощью Windows App SDK, что приводит к тому, что меню с многоточием становится неподвижным после отображения карточки настройки.
В типичных сценариях настройки мини-приложения пользователь выбирает, какие данные отображаются в мини-приложении или настраивает визуальную презентацию мини-приложения. Для простоты в этом разделе будет добавлено поведение настройки, позволяющее пользователю сбросить счетчик мини-приложения подсчета, реализованного на предыдущих шагах.
Примечание.
Настройка виджетов поддерживается только в пакете 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, который отправляет наши данные и шаблоны визуальные 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 , что и при взаимодействии пользователя с обычным интерфейсом мини-приложения. Для поддержки настройки мы ищем глаголы "reset" и "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. Нажмите кнопку Выйти из настройки, чтобы вернуться к обычному поведению вашего мини-приложения.
Windows developer