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


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

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

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

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

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

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

  • Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Включение устройства для разработки".
  • Visual Studio 2022 или более поздней версии с рабочей нагрузкой разработки универсальная платформа Windows. Обязательно добавьте компонент для 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 щелкните проект правой ExampleWidgetProvider кнопкой мыши в Обозреватель решений и выберите "Класс надстройки>". В диалоговом окне "Добавить класс" назовите класс "WidgetProvider" и нажмите кнопку "Добавить". В созданном WidgetProvider.cs файле обновите определение класса, чтобы указать, что он реализует интерфейс IWidgetProvider .

// 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 шаблона мини-приложения см. в статье "Создание шаблона мини-приложения с помощью конструктора адаптивных карточек".

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

Узел мини-приложения вызывает 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

Узел мини-приложения вызывает 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, а затем мини-код, чтобы получить идентификатор обновляемого мини-приложения. Найдите запись в наших включенных мини-приложениях с указанным идентификатором, а затем обновите настраиваемое значение состояния, которое используется для хранения числа добавок. Наконец, обновите содержимое мини-приложения новым значением с помощью вспомогательной функции 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 данных на основе того, на каком мини-приложении обновляется мини-приложение. Мини-приложениеUpdateRequestOptions инициализируется шаблоном, данными и пользовательским состоянием для обновляемого мини-приложения. Вызовите 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 = widgetName, widgetName = widgetId };
            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 щелкните проект правой ExampleWidgetProvider кнопкой мыши в Обозреватель решений и выберите "Класс надстройки>". В диалоговом окне "Добавить класс" назовите класс "FactoryHelper" и нажмите кнопку "Добавить".

Замените содержимое файла 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>. Выберите проект 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 щелкните правой кнопкой мыши проект ExampleWidgetProvider в Обозреватель решений и выберите "Свойства". В разделе "Общие приложения>" измените тип выходных данных с "Консольное приложение" на "Приложение Windows".

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

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

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

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

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

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

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

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

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

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

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

Примечание.

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

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

Примечание.

Настройка мини-приложения поддерживается только в пакете 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 , который отправляет наши данные и шаблоны visual 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 , что и при взаимодействии пользователя с обычным интерфейсом мини-приложения. Для поддержки настройки мы ищем команды "сброс" и "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. Нажмите кнопку "Выйти", чтобы вернуться к обычному поведению мини-приложения.