Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описывается создание простого поставщика мини-приложений, реализующего интерфейс IWidgetProvider . Методы этого интерфейса вызываются узлом мини-приложения, чтобы запросить данные, определяющие мини-приложение, или разрешить поставщику мини-приложений реагировать на действие пользователя в мини-приложении. Поставщики мини-приложений могут поддерживать одно мини-приложение или несколько мини-приложений. В этом примере мы определим два разных мини-приложения. Одним из виджетов является макет погодного виджета, иллюстрирующий некоторые варианты форматирования, предоставляемые платформой Adaptive Cards. Второе мини-приложение будет отображать действия пользователя и функцию пользовательского состояния мини-приложения, сохраняя счетчик, который увеличивается всякий раз, когда пользователь нажимает кнопку, отображаемую на мини-приложении.
Этот пример кода в этой статье адаптирован из примера мини-приложений пакета SDK для Windows. Сведения о реализации поставщика мини-приложений с помощью C++/WinRT см. в статье Реализация поставщика мини-приложений win32 (C++/WinRT).
Требования
- Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Параметры" для разработчиков.
- Visual Studio 2026 или более поздней версии с рабочей нагрузкой разработки приложений WinUI . Обязательно добавьте компонент для C++ (версии 143) из дополнительного раскрывающегося списка.
Создание консольного приложения C#
В Visual Studio создайте проект . В диалоговом окне "Создание проекта" установите для фильтра языка значение "C#" и фильтр платформы в Windows, а затем выберите шаблон проекта консольного приложения. Присвойте новому проекту имя ExampleWidgetProvider. При появлении запроса задайте для целевой версии .NET значение 8.0.
Когда проект загружается, в Обозреватель решений щелкните правой кнопкой мыши имя проекта и выберите "Свойства". На странице "Общие" прокрутите вниз до целевой ОС и выберите "Windows". В разделе "Целевая версия ОС" выберите версию 10.0.19041.0 или более поздней.
Чтобы обновить проект для поддержки .NET 8.0, в Обозреватель решений щелкните правой кнопкой мыши имя проекта и выберите "Изменить файл проекта". В PropertyGroup добавьте следующий элемент RuntimeIdentifiers.
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
Обратите внимание, что в этом пошаговом руководстве используется консольное приложение, отображающее окно консоли при активации мини-приложения для упрощения отладки. Когда вы будете готовы опубликовать приложение поставщика мини-приложений, вы можете преобразовать консольное приложение в приложение Windows, выполнив действия, описанные в разделе "Преобразование консольного приложения в приложение Windows".
Добавление ссылок на пакет SDK для приложений Windows
В этом примере используется последний стабильный пакет Пакета NuGet для приложений Windows. В Обозреватель решений щелкните правой кнопкой мыши зависимости и выберите пункт "Управление пакетами NuGet...". В диспетчере пакетов NuGet перейдите на вкладку "Обзор" и найдите "Microsoft.WindowsAppSDK". Выберите последнюю стабильную версию в раскрывающемся списке "Версия" , а затем нажмите кнопку "Установить".
Добавление класса WidgetProvider для обработки операций мини-приложения
В Visual Studio щелкните правой кнопкой мыши по проекту
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider
Подготовка к отслеживанию включенных мини-приложений
Поставщик мини-приложений может поддерживать одно мини-приложение или несколько мини-приложений. Каждый раз, когда узел мини-приложения инициирует операцию с поставщиком мини-приложений, он передает идентификатор для идентификации мини-приложения, связанного с операцией. Каждое мини-приложение также имеет связанное имя и значение стейта, которое можно использовать для хранения пользовательских данных. В этом примере мы объявим простую вспомогательную структуру для хранения идентификатора, имени и данных для каждого закрепленного виджета. Мини-приложения также могут находиться в активном состоянии, которое рассматривается в разделе "Активация и деактивация " ниже, и мы отслеживаем это состояние для каждого мини-приложения с логическим значением. Добавьте следующее определение в файл WidgetProvider.cs внутри пространства имен ExampleWidgetProvider , но за пределами определения класса WidgetProvider .
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string? widgetId { get; set; }
public string? widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
}
В определении класса WidgetProvider в WidgetProvider.cs добавьте элемент для карты, который будет поддерживать список включенных мини-приложений, используя идентификатор мини-приложения в качестве ключа для каждой записи.
// WidgetProvider.cs
// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>();
Объявите строки JSON шаблона виджета
В этом примере будут объявлены некоторые статические строки для создания шаблонов JSON для каждого виджета. Для удобства эти шаблоны хранятся в переменных-членах класса WidgetProvider . Если вам нужно общее хранилище для шаблонов, их можно включить в состав пакета приложения: Доступ к файлам пакетов. Сведения о создании документа JSON шаблона мини-приложения см. в статье "Создание шаблона мини-приложения с помощью конструктора адаптивных карточек".
В последнем выпуске приложения, реализующие мини-приложения Windows, могут настраивать заголовок, отображаемый для их мини-приложения в доске мини-приложений, переопределяя презентацию по умолчанию. Для получения дополнительной информации см. в разделе Настройка области заголовка виджета.
Примечание.
В последней версии приложения, реализующие виджеты Windows, могут заполнять содержимое виджетов с помощью HTML, предоставленного по указанному URL-адресу, вместо передачи содержимого в формате схемы адаптивной карты в JSON-данных, направляемых от поставщика на панель виджетов. Поставщики мини-приложений по-прежнему должны предоставлять нагрузку JSON адаптивной карточки, поэтому шаги реализации в этом руководстве применимы к веб-мини-приложениям. Дополнительные сведения см. у поставщиков веб-мини-приложений .
// WidgetProvider.cs
// Class members of WidgetProvider
const string weatherWidgetTemplate = """
{
"$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 string countWidgetTemplate = """
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "You have clicked the button ${count} times"
},
{
"text":"Rendering Only if Small",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"small\"}"
},
{
"text":"Rendering Only if Medium",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"medium\"}"
},
{
"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 , который вызывается в нескольких из этих реализаций методов, будет показан далее в этой статье.
Примечание.
Объекты, передаваемые в методы обратного вызова интерфейса IWidgetProvider, гарантированно допустимы только в пределах этого вызова. Не следует хранить ссылки на эти объекты, так как их поведение вне контекста обратного вызова не определено.
СоздатьВиджет
Хост мини-приложений вызывает CreateWidget когда пользователь закрепляет одно из ваших мини-приложений в нем. Сначала этот метод получает идентификатор и имя связанного мини-приложения и добавляет новый экземпляр вспомогательной структуры CompactWidgetInfo в коллекцию включенных мини-приложений. Затем мы отправим исходный шаблон и данные для мини-приложения, который инкапсулируется в вспомогательный метод UpdateWidget .
// WidgetProvider.cs
public void CreateWidget(WidgetContext widgetContext)
{
var widgetId = widgetContext.Id; // To save RPC calls
var widgetName = widgetContext.DefinitionId;
CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetId, widgetName = widgetName };
RunningWidgets[widgetId] = runningWidgetInfo;
// Update the widget
UpdateWidget(runningWidgetInfo);
}
Удалить виджет
Хост виджетов вызывает DeleteWidget, когда пользователь открепляет один из ваших виджетов от хоста виджетов. Когда это происходит, мы удалим связанное мини-приложение из списка включенных мини-приложений, чтобы мы не отправляли дальнейшие обновления для этого мини-приложения.
// WidgetProvider.cs
public void DeleteWidget(string widgetId, string customState)
{
RunningWidgets.Remove(widgetId);
if(RunningWidgets.Count == 0)
{
emptyWidgetListEvent.Set();
}
}
В этом примере в дополнение к удалению мини-приложения с указанным из списка включенных мини-приложений мы также проверяем, пусто ли список, и если да, мы зададим событие, которое будет использоваться позже, чтобы разрешить приложению выйти, если не включены мини-приложения. В определении класса добавьте объявление ManualResetEvent и публичную функцию доступа.
// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);
public static ManualResetEvent GetEmptyWidgetListEvent()
{
return emptyWidgetListEvent;
}
OnActionInvoked
Хост виджета вызывает OnActionInvoked, когда пользователь взаимодействует с действием, определенным в шаблоне виджета. Для виджета-счетчика, использованного в этом примере, действие было объявлено со значением глагола "inc" в JSON-шаблоне виджета. Код поставщика мини-приложений будет использовать это значение команды , чтобы определить, какие действия следует предпринять в ответ на взаимодействие пользователя.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
В методе OnActionInvoked получите значение команды, проверив свойство Verb объекта WidgetActionInvokedArgs, переданное в метод. Если глагол — "inc", то мы знаем, что собираемся увеличить счетчик в пользовательском состоянии для виджета. Из WidgetActionInvokedArgs получите объект WidgetContext, а затем WidgetId, чтобы получить ID обновляемого мини-приложения. Найдите запись в карте наших активированных виджетов с указанным идентификатором, а затем обновите пользовательское значение состояния, используемое для хранения количества увеличений. Наконец, обновите содержимое мини-приложения новым значением с помощью вспомогательной функции UpdateWidget .
// WidgetProvider.cs
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var verb = actionInvokedArgs.Verb;
if (verb == "inc")
{
var 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:
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
}
Сведения о синтаксисе Action.Execute для адаптивных карточек см. в разделе Action.Execute. Рекомендации по проектированию взаимодействия с мини-приложениями см . в руководстве по проектированию взаимодействия с мини-приложениями
OnWidgetContextChanged (Изменение контекста виджета)
В текущем выпуске OnWidgetContextChanged вызывается только при изменении пользовательского размера закрепленного виджета. Вы можете вернуть другой шаблон или данные JSON в хост виджета в зависимости от запрашиваемого размера. Вы также можете разработать шаблон JSON для поддержки всех доступных размеров с помощью условной разметки на основе значения host.widgetSize. Если вам не нужно отправлять новый шаблон или данные для учета изменения размера, можно использовать OnWidgetContextChanged для целей телеметрии.
// WidgetProvider.cs
public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs)
{
var widgetContext = contextChangedArgs.WidgetContext;
var widgetId = widgetContext.Id;
var widgetSize = widgetContext.Size;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
UpdateWidget(localWidgetInfo);
}
}
Активация и деактивация
Метод Activate вызывается, чтобы уведомить поставщика мини-приложений о том, что хост мини-приложения в настоящее время заинтересован в получении обновленного содержимого от поставщика. Например, это может означать, что пользователь в настоящее время активно просматривает хост виджета. Метод Деактивировать вызывается для уведомления поставщика виджетов о том, что хост виджета больше не запрашивает обновления содержимого. Эти два метода определяют окно, в котором хост виджета больше всего заинтересован в отображении самого актуального содержимого. Поставщики мини-приложений могут отправлять обновления в любое время, например, в ответ на push-уведомление, но, как и в любой задаче, выполняемой в фоновом режиме, важно балансировать между предоставлением актуального контента и вопросами ресурсов, такими как срок службы батареи.
Активировать и Деактивировать вызываются для каждого виджета. В этом примере отслеживается активное состояние каждого мини-приложения в вспомогательной структуре CompactWidgetInfo . В методе Activate мы вызываем вспомогательный метод UpdateWidget для обновления мини-приложения. Обратите внимание, что интервал времени между активацией и деактивацией может быть небольшим, поэтому рекомендуется постараться оптимизировать процесс обновления вашего виджета, чтобы сделать его как можно более быстрым.
// WidgetProvider.cs
public void Activate(WidgetContext widgetContext)
{
var widgetId = widgetContext.Id;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.isActive = true;
UpdateWidget(localWidgetInfo);
}
}
public void Deactivate(string widgetId)
{
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.isActive = false;
}
}
Обновление мини-приложения
Определите вспомогательный метод UpdateWidget для обновления включенного мини-приложения. В этом примере мы проверяем имя мини-приложения во вспомогательной структуре CompactWidgetInfo, переданной в метод, а затем устанавливаем соответствующий шаблон и данные JSON на основе того, какое мини-приложение обновляется. WidgetUpdateRequestOptions инициализируется с использованием шаблона, данных и кастомного состояния для обновляемого виджета. Вызовите WidgetManager::GetDefault, чтобы получить экземпляр класса WidgetManager, а затем вызовите UpdateWidget, чтобы отправить обновленные данные мини-приложения на узел мини-приложения.
// WidgetProvider.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(localWidgetInfo.widgetId);
string? templateJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
templateJson = weatherWidgetTemplate.ToString();
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
templateJson = countWidgetTemplate.ToString();
}
string? dataJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
dataJson = "{}";
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
dataJson = "{ \"count\": " + localWidgetInfo.customState.ToString() + " }";
}
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= localWidgetInfo.customState.ToString();
WidgetManager.GetDefault().UpdateWidget(updateOptions);
}
Инициализация списка включенных мини-приложений при запуске
Когда наш поставщик мини-приложений инициализируется впервые, рекомендуется обратиться к WidgetManager, чтобы проверить, есть ли запущенные мини-приложения, которые в настоящее время обслуживаются нашим поставщиком. Это поможет восстановить приложение до предыдущего состояния в случае перезагрузки компьютера или сбоя поставщика. Вызовите WidgetManager.GetDefault, чтобы получить экземпляр диспетчера виджетов по умолчанию. Затем вызовите GetWidgetInfos, который возвращает массив объектов WidgetInfo. Скопируйте идентификаторы виджетов, имена и пользовательское состояние во вспомогательную структуру CompactWidgetInfo и сохраните её в члену переменной RunningWidgets. Вставьте следующий код в определение класса для класса WidgetProvider .
// WidgetProvider.cs
public WidgetProvider()
{
var runningWidgets = WidgetManager.GetDefault().GetWidgetInfos();
foreach (var widgetInfo in runningWidgets)
{
var widgetContext = widgetInfo.WidgetContext;
var widgetId = widgetContext.Id;
var widgetName = widgetContext.DefinitionId;
var customState = widgetInfo.CustomState;
if (!RunningWidgets.ContainsKey(widgetId))
{
CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetId, widgetName = 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 = Convert.ToInt32(customState.ToString());
runningWidgetInfo.customState = count;
}
catch
{
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
Реализация фабрики классов, которая создаст экземпляр WidgetProvider по запросу
Чтобы хост виджета взаимодействовал с нашим поставщиком виджетов, необходимо вызвать CoRegisterClassObject. Для этой функции требуется создать реализацию IClassFactory , которая создаст объект класса для нашего класса WidgetProvider . Мы реализуем фабрику классов в автономном вспомогательном классе.
В Visual Studio щелкните правой кнопкой мыши по проекту
Замените содержимое файла FactoryHelper.cs следующим кодом. Этот код определяет интерфейс IClassFactory и реализует два метода: CreateInstance и LockServer. Этот код является типичным шаблоном для реализации фабрики классов и не зависит от функциональных возможностей поставщика мини-приложений, за исключением того, что созданный объект класса реализует интерфейс IWidgetProvider .
// FactoryHelper.cs
using Microsoft.Windows.Widgets.Providers;
using System.Runtime.InteropServices;
using WinRT;
namespace COM
{
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
///
/// IClassFactory declaration
///
[ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
internal interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
[ComVisible(true)]
class WidgetProviderFactory<T> : IClassFactory
where T : IWidgetProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == typeof(T).GUID || riid == Guid.Parse(COM.Guids.IUnknown))
{
// Create the instance of the .NET object
ppvObject = MarshalInspectable<IWidgetProvider>.FromManaged(new T());
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
return 0;
}
int IClassFactory.LockServer(bool fLock)
{
return 0;
}
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
}
}
Создайте GUID, представляющий CLSID для поставщика виджетов
Затем необходимо создать GUID, который будет представлять CLSID, используемый для идентификации поставщика виджетов для активации COM. То же значение также будет использоваться при упаковке приложения. Создайте GUID в Visual Studio, перейдя в раздел "Сервис-создание> GUID". Выберите параметр формата реестра и нажмите кнопку "Копировать ", а затем вставьте его в текстовый файл, чтобы его можно было скопировать позже.
Регистрация объекта класса поставщика мини-приложений с помощью OLE
В файле Program.cs исполняемого файла мы вызовем CoRegisterClassObject для регистрации поставщика виджетов в OLE, чтобы узел виджетов смог взаимодействовать с ним. Замените содержимое Program.cs приведенным ниже кодом. Этот код импортирует функцию CoRegisterClassObject и вызывает ее, передав интерфейс WidgetProviderFactory , определенный на предыдущем шаге. Обязательно обновите объявление переменной CLSID_Factory, чтобы использовать созданный на предыдущем шаге GUID.
// Program.cs
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using Microsoft.Windows.Widgets;
using ExampleWidgetProvider;
using COM;
using System;
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("ole32.dll")]
static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
[DllImport("ole32.dll")] static extern int CoRevokeClassObject(uint dwRegister);
Console.WriteLine("Registering Widget Provider");
uint cookie;
Guid CLSID_Factory = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
CoRegisterClassObject(CLSID_Factory, new WidgetProviderFactory<WidgetProvider>(), 0x4, 0x1, out cookie);
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();
if (GetConsoleWindow() != IntPtr.Zero)
{
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();
}
else
{
// Wait until the manager has disposed of the last widget provider.
using (var emptyWidgetListEvent = WidgetProvider.GetEmptyWidgetListEvent())
{
emptyWidgetListEvent.WaitOne();
}
CoRevokeClassObject(cookie);
}
Обратите внимание, что этот пример кода импортирует функцию GetConsoleWindow , чтобы определить, работает ли приложение в качестве консольного приложения, поведение по умолчанию для этого пошагового руководства. Если функция возвращает допустимый указатель, мы записываем сведения об отладке в консоль. В противном случае приложение работает в качестве приложения Для Windows. В этом случае мы ждем события, заданного в методе DeleteWidget , когда список включенных мини-приложений пуст, и мы выходим из приложения. Сведения о преобразовании примера консольного приложения в приложение Windows см. в статье "Преобразование консольного приложения в приложение Windows".
Упакуйте приложение поставщика виджетов
В текущем выпуске только упакованные приложения можно зарегистрировать в качестве поставщиков мини-приложений. Ниже описано, как упаковать приложение и обновить манифест приложения, чтобы зарегистрировать приложение в качестве поставщика мини-приложений.
Создание проекта упаковки 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 в Обозреватель решений и выберите Свойства. В разделе "Общие приложения>" измените тип выходных данных с "Консольное приложение" на "Приложение Windows".
Публикация виджета
После разработки и тестирования мини-приложения вы можете опубликовать приложение в Microsoft Store, чтобы пользователи могли устанавливать мини-приложения на своих устройствах. Пошаговые инструкции по публикации приложения см. в статье "Публикация приложения в Microsoft Store".
Коллекция магазинов мини-приложений
После публикации приложения в Microsoft Store вы можете запросить включение приложения в коллекцию магазинов мини-приложений, которая помогает пользователям обнаруживать приложения с мини-приложениями, включающими мини-приложения Windows. Чтобы отправить запрос, см. Отправка информации о вашем мини-приложении для добавления в коллекцию Магазина.
Настройка виджета
Начиная с пакета SDK для приложений Windows 1.4 мини-приложения могут поддерживать настройку пользователей. При реализации этой функции опция Настройка мини-приложения добавляется в меню с троеточием над опцией Открепить мини-приложение.
Ниже приведены инструкции по настройке мини-приложений.
- В обычном режиме работы поставщик виджетов отвечает на запросы хоста виджетов, предоставляя шаблон и полезные данные для стандартного использования виджетов.
- Пользователь нажимает кнопку "Настроить виджет" в меню с троеточием.
- Мини-приложение вызывает событие OnCustomizationRequested в поставщике мини-приложений, чтобы указать, что пользователь запрашивал возможность настройки мини-приложения.
- Поставщик мини-приложений задает внутренний флаг, указывающий, что мини-приложение находится в режиме настройки. В режиме настройки поставщик мини-приложений отправляет шаблоны JSON для пользовательского интерфейса настройки мини-приложения вместо обычного пользовательского интерфейса мини-приложения.
- В режиме настройки поставщик мини-приложения получает события OnActionInvoked, когда пользователь взаимодействует с пользовательским интерфейсом настройки и регулирует внутреннюю конфигурацию и поведение на основе действий пользователя.
- Если действие, связанное с событием OnActionInvoked, является действием "выход из режима настройки", определённым в приложении, поставщик виджета сбрасывает свой внутренний флаг, чтобы указать, что режим настройки завершён, и возобновляет отправку обычных JSON-шаблонов визуальных данных для основного опыта использования виджета, отражая изменения, запрошенные при настройке. Пользователь может закрыть взаимодействие с настройками без нажатия действия выхода из настройки, определенного приложением. В этом случае будет вызываться IWidgetProviderAnalytics.OnAnalyticsInfoReported, и WidgetAnalyticsInfoReportedArgs.AnalyticsJson будет иметь тип взаимодействия "exitCustomization".
- Поставщик мини-приложений сохраняет параметры настройки на диске или в облаке, чтобы изменения сохранялись между вызовами поставщика мини-приложений.
Примечание.
Существует известная ошибка с панелью виджетов Windows для виджетов, созданных с помощью Windows App SDK, что приводит к тому, что меню с многоточием становится неподвижным после отображения карточки настройки.
В типичных сценариях настройки мини-приложения пользователь выбирает, какие данные отображаются в мини-приложении или настраивает визуальную презентацию мини-приложения. Для простоты в этом разделе будет добавлено поведение настройки, позволяющее пользователю сбросить счетчик мини-приложения подсчета, реализованного на предыдущих шагах.
Примечание.
Настройка виджетов поддерживается только в пакете SDK для приложений Windows 1.4 и в более поздних версиях. Обязательно обновите ссылки в проекте до последней версии пакета Nuget.
Обновление манифеста пакета для объявления поддержки настройки
Чтобы сообщить узлу мини-приложения, что мини-приложение поддерживает настройку, добавьте атрибут IsCustomizableв элемент Definition для мини-приложения и задайте для него значение true.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
Отслеживание того, когда мини-приложение находится в режиме настройки
В примере в этой статье используется вспомогательная структура CompactWidgetInfo для отслеживания текущего состояния наших активных мини-приложений. Добавьте поле inCustomization, которое будет использоваться для отслеживания того, когда хост виджета ожидает, что мы отправим шаблон JSON настройки, а не мини-приложения с обычным шаблоном.
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string widgetId { get; set; }
public string widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
public bool inCustomization = false;
}
Реализовать IWidgetProvider2
Функция настройки мини-приложения предоставляется через интерфейс IWidgetProvider2 . Обновите определение класса WidgetProvider, чтобы реализовать этот интерфейс.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2
Добавьте реализацию для обратного вызова OnCustomizationRequested интерфейса IWidgetProvider2. Этот метод использует тот же шаблон, что и те обратные вызовы, которые мы применяли ранее. Мы получаем идентификатор мини-приложения для настройки из WidgetContext, находим вспомогательную структуру CompactWidgetInfo, связанную с этим мини-приложением, и задаем для поля inCustomization значение true.
// WidgetProvider.cs
public void OnCustomizationRequested(WidgetCustomizationRequestedArgs customizationInvokedArgs)
{
var widgetId = customizationInvokedArgs.WidgetContext.Id;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.inCustomization = true;
UpdateWidget(localWidgetInfo);
}
}
Теперь объявите строковую переменную, которая определяет шаблон JSON для пользовательского интерфейса настройки мини-приложения. В этом примере у нас есть кнопка "Сброс счетчика" и кнопка "Выйти из настройки", которая сигнализирует нашему поставщику вернуться к обычному поведению мини-приложений. Поместите это определение рядом с другими определениями шаблонов.
// WidgetProvider.cs
const string countWidgetCustomizationTemplate = @"
{
""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""
}";
Отправка шаблона настройки в UpdateWidget
Затем мы обновим метод-помощник UpdateWidget, который отправляет наши данные и шаблоны визуальные JSON на хост виджета. При обновлении мини-приложения подсчета мы отправим обычный шаблон мини-приложения или шаблон настройки в зависимости от значения поля inCustomization . Для краткости код, который не относится к настройке, опущен в этом фрагменте кода.
// WidgetProvider.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
...
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
if (!localWidgetInfo.inCustomization)
{
templateJson = countWidgetTemplate.ToString();
}
else
{
templateJson = countWidgetCustomizationTemplate.ToString();
}
}
...
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 = localWidgetInfo.customState.ToString();
WidgetManager.GetDefault().UpdateWidget(updateOptions);
}
Реагирование на действия по настройке
Когда пользователи взаимодействуют с входными данными в шаблоне настройки, он вызывает тот же обработчик OnActionInvoked , что и при взаимодействии пользователя с обычным интерфейсом мини-приложения. Для поддержки настройки мы ищем глаголы "reset" и "exitCustomization" в JSON-шаблоне настройки. Если действие предназначено для кнопки "Сброс счетчика", мы сбросим счетчик, удерживаемый в поле customState вспомогательной структуры, значение 0. Если действие предназначено для кнопки "Выход из настройки", мы установите для поля inCustomization значение false, чтобы при вызове UpdateWidget наш вспомогательный метод отправлял обычные шаблоны JSON, а не шаблон настройки.
// WidgetProvider.cs
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var verb = actionInvokedArgs.Verb;
if (verb == "inc")
{
var 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:
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == "reset")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Reset the count
localWidgetInfo.customState = 0;
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == "exitCustomization")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Stop sending the customization template
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
}
Теперь при развертывании мини-приложения вы увидите кнопку "Настроить мини-приложение " в меню многоточия. При нажатии кнопки настройки отобразится шаблон настройки.
Нажмите кнопку "Сброс счетчика", чтобы сбросить счетчик до 0. Нажмите кнопку Выйти из настройки, чтобы вернуться к обычному поведению вашего мини-приложения.
Windows developer