Implementar um provedor de widget em um aplicativo C# do Windows

Este artigo explica como criar um provedor de widget simples que implemente a interface IWidgetProvider. Os métodos dessa interface são invocados pelo host do widget para solicitar os dados que definem um widget ou para permitir que o provedor de widget responda a uma ação do usuário em um widget. Os provedores de widget podem dar suporte a um só widget ou a vários widgets. Neste exemplo, definiremos dois widgets diferentes. Um widget é de clima fictício que ilustra algumas das opções de formatação fornecidas pela estrutura Cartões Adaptáveis. O segundo widget demonstrará as ações do usuário e o recurso de estado do widget personalizado mantendo um contador incrementado sempre que o usuário clicar em um botão exibido no widget.

A screenshot of a simple weather widget. The widget shows some weather-related graphics an data as well as some diagnostic text illustrating that the template for the medium size widget is being displayed.

A screenshot of a simple counting widget. The widget shows a string containing the numeric value to be incremented and a button labeled Increment, as well as some diagnostic text illustrating that the template for the small size widget is being displayed.

O código de exemplo neste artigo foi adaptado do exemplo de widgets do SDK do Aplicativo Windows. Para implementar um provedor de widget usando C++/WinRT, confira Implementar um provedor de widget em um aplicativo win32 (C++/WinRT).

Pré-requisitos

  • O dispositivo precisa ter o modo de desenvolvedor habilitado. Para obter mais informações, confira Habilitar o dispositivo para desenvolvimento.
  • Visual Studio 2022 ou posterior com a carga de trabalho desenvolvimento da Plataforma Universal do Windows. Adicione o componente para C++ (v143) na lista suspensa opcional.

Criar um aplicativo de console C#

No Visual Studio, crie um novo projeto. Na caixa de diálogo Criar um projeto, defina o filtro de linguagem como "C#" e o filtro de plataforma como Windows e, em seguida, selecione o modelo de projeto Aplicativo de Console. Nomeie o novo projeto como "ExampleWidgetProvider". Quando solicitado, defina a versão do .NET de destino como 6.0.

Quando o projeto for carregado, no Gerenciador de Soluções clique com o botão direito do mouse no nome do projeto e selecione Propriedades. Na página Geral, role para baixo até SO de destino e selecione "Windows". Em Versão do SO de destino, selecione a versão 10.0.19041.0 ou posterior.

Observe que este passo a passo usa um aplicativo de console que exibe a janela do console quando o widget é ativado para facilitar a depuração. Quando estiver pronto para publicar o aplicativo de provedor de widget, você poderá converter o aplicativo de console em um aplicativo do Windows seguindo as etapas em Converter o aplicativo de console em um aplicativo do Windows.

Adicionar referências aos pacotes NuGet da Biblioteca de Implementação do Windows e do SDK do Aplicativo Windows

Este exemplo usa o pacote NuGet do SDK do Aplicativo Windows estável mais recente. No Gerenciador de Soluções, clique com o botão direito do mouse em Dependências e selecione Gerenciar pacotes NuGet.... No gerenciador de pacotes NuGet, selecione a guia Procurar e pesquise "Microsoft.WindowsAppSDK". Selecione a versão estável mais recente na lista suspensa Versão e clique em Instalar.

Adicionar uma classe WidgetProvider para lidar com operações de widget

No Visual Studio, clique com o botão direito do mouse no projeto ExampleWidgetProvider no Gerenciador de Soluções e selecione Adicionar –> Classe. No diálogo Adicionar classe, nomeie a classe como "WidgetProvider" e clique em Adicionar. No arquivo WidgetProvider.cs gerado, atualize a definição de classe para indicar que ela implementa a interface IWidgetProvider.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider

Preparação para acompanhar widgets habilitados

Um provedor de widget pode dar suporte a um só widget ou a vários widgets. Sempre que o host do widget inicia uma operação com o provedor de widget, ele passa uma ID para identificar o widget associado à operação. Cada widget também tem um nome associado e um valor de estado que podem ser usados para armazenar dados personalizados. Neste exemplo, vamos declarar uma estrutura auxiliar simples para armazenar a ID, o nome e os dados de cada widget fixado. Os widgets também podem estar em um estado ativo, que é discutido na seção Ativar e Desativar abaixo, e vamos acompanhar esse estado para cada widget com um valor booliano. Adicione a definição a seguir ao arquivo WidgetProvider.cs, dentro do namespace ExampleWidgetProvider, mas fora da definição da classe WidgetProvider.

// WidgetProvider.cs

public class CompactWidgetInfo
{
    public string widgetId { get; set; }
    public string widgetName { get; set; }
    public int customState = 0;
    public bool isActive = false;

}

Dentro da declaração de classe WidgetProvider em WidgetProvider.h, adicione um membro para o mapa que manterá a lista de widgets habilitados, usando a ID do widget como a chave de cada entrada.

// WidgetProvider.cs

// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>(); 

Declarar cadeias de caracteres JSON do modelo de widget

Este exemplo vai declarar algumas cadeias de caracteres estáticas para definir os modelos JSON de cada widget. Para facilitar o trabalho, esses modelos são armazenados nas variáveis de membro da classe WidgetProvider. Se você precisar de um armazenamento geral para os modelos, eles poderão ser incluídos como parte do pacote de aplicativos: Acessar arquivos de pacote. Para obter informações de como criar o documento JSON do modelo de widget, confira Criar um modelo de widget com o Designer de Cartão Adaptável.

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

Implementar os métodos IWidgetProvider

Nas próximas seções, vamos implementar os métodos da interface IWidgetProvider. O método auxiliar UpdateWidget chamado em várias dessas implementações de método será mostrado mais adiante neste artigo.

Observação

Os objetos passados aos métodos de retorno de chamada da interface IWidgetProvider só têm garantia de serem válidos dentro do retorno de chamada. Você não deve armazenar referências a esses objetos porque o comportamento fora do contexto do retorno de chamada é indefinido.

CreateWidget

O host do widget chama CreateWidget quando o usuário fixa um dos widgets do aplicativo no host do widget. Primeiro, esse método obtém a ID e o nome do widget associado e adiciona uma nova instância da estrutura auxiliar, CompactWidgetInfo, à coleção de widgets habilitados. Em seguida, enviamos o modelo inicial e os dados do widget, que é encapsulado no método auxiliar 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

O host do widget chama DeleteWidget quando o usuário desafixa um dos widgets do aplicativo do host do widget. Quando isso ocorre, removemos o widget associado da lista de widgets habilitados para que não sejam mais enviadas atualizações desse widget.

// WidgetProvider.cs

public void DeleteWidget(string widgetId, string customState)
{
    RunningWidgets.Remove(widgetId);

    if(RunningWidgets.Count == 0)
    {
        emptyWidgetListEvent.Set();
    }
}

Neste exemplo, além de remover o widget com o especificado da lista de widgets habilitados, também verificamos se a lista está vazia e, nesse caso, definimos um evento que será usado mais tarde para permitir que o aplicativo seja encerrado quando não houver widgets habilitados. Dentro da definição de classe, adicione a declaração de ManualResetEvent e uma função de acessador público.

// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);

public static ManualResetEvent GetEmptyWidgetListEvent()
{
    return emptyWidgetListEvent;
}

OnActionInvoked

O host do widget chama OnActionInvoked quando o usuário interage com uma ação que você definiu no modelo de widget. Para o widget de contador usado neste exemplo, uma ação foi declarada com um valor de verbo igual a "inc" no modelo JSON do widget. O código do provedor de widget usará esse valor de verbo para determinar qual ação tomar em resposta à interação do usuário.

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

No método OnActionInvoked, obtenha o valor do verbo verificando a propriedade Verb do WidgetActionInvokedArgs passado para o método. Se o verbo for "inc", saberemos que vamos incrementar a contagem no estado personalizado para o widget. Em WidgetActionInvokedArgs, obtenha o objeto WidgetContext e, em seguida, o WidgetId para obter a ID do widget que está sendo atualizado. Localize a entrada no mapa de widgets habilitados com a ID especificada e atualize o valor de estado personalizado usado para armazenar o número de incrementos. Por fim, atualize o conteúdo do widget com o novo valor usando a função auxiliar 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);
            }
        }
}

Para obter informações sobre a sintaxe Action.Execute de Cartões Adaptáveis, confira Action.Execute. Para obter diretrizes de como projetar a interação dos widgets, confira Diretrizes de design de interação do widget

OnWidgetContextChanged

Na versão atual, OnWidgetContextChanged só é chamado quando o usuário altera o tamanho de um widget fixado. Você pode optar por retornar um modelo/dados JSON diferente para o host do widget, dependendo do tamanho solicitado. Você também pode criar o modelo JSON para dar suporte a todos os tamanhos disponíveis usando a renderização condicional com base no valor de host.widgetSize. Se você não precisar enviar um novo modelo ou dados para considerar a alteração de tamanho, use OnWidgetContextChanged para fins de telemetria.

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

O método Activate é chamado para notificar o provedor de widget de que o host do widget quer receber conteúdo atualizado do provedor no momento. Por exemplo, isso pode significar que o usuário está exibindo ativamente o host do widget no momento. O método Deactivate é chamado para notificar o provedor de widget de que o host do widget não está mais solicitando atualizações de conteúdo. Esses dois métodos definem uma janela na qual o host do widget prefere mostrar o conteúdo mais atualizado. Os provedores de widget podem enviar atualizações para o widget a qualquer momento, como em resposta a uma notificação por push, mas, como em qualquer tarefa em segundo plano, é importante equilibrar o fornecimento de conteúdo atualizado com questões de recursos, como a duração da bateria.

Activate e Deactivate são chamados por widget. Este exemplo acompanha o status ativo de cada widget no struct auxiliar CompactWidgetInfo. No método Activate, chamamos o método auxiliar UpdateWidget para atualizar o widget. Observe que a janela de tempo entre Activate e Deactivate pode ser pequena, portanto, é recomendável que você tente acelerar ao máximo o caminho do código de atualização do widget.

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

Atualizar um widget

Defina o método auxiliar UpdateWidget para atualizar um widget habilitado. Neste exemplo, marcamos o nome do widget no struct auxiliar CompactWidgetInfo passado ao método e, em seguida, definimos o modelo apropriado e o JSON de dados com base no widget que está sendo atualizado. Um WidgetUpdateRequestOptions é inicializado com o modelo, os dados e o estado personalizado do widget que está sendo atualizado. Chame WidgetManager::GetDefault para obter uma instância da classe WidgetManager e, em seguida, chame UpdateWidget para enviar os dados atualizados do widget ao host do widget.

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

Inicializar a lista de widgets habilitados na inicialização

Quando o provedor de widget é inicializado pela primeira vez, é bom perguntar ao WidgetManager se há widgets em execução que o provedor está fornecendo no momento. Isso ajudará a recuperar o aplicativo para o estado anterior em caso de reinicialização do computador ou falha do provedor. Chame WidgetManager::GetDefault para obter a instância do gerenciador de widget padrão do aplicativo. Em seguida, chame GetWidgetInfos, que retorna uma matriz de objetos WidgetInfo. Copie as IDs, os nomes e o estado personalizado do widget no struct auxiliar CompactWidgetInfo e salve-o na variável de membro RunningWidgets. Cole o código a seguir no construtor de classe 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;
        }
    }
}

Implementar uma fábrica de classes que criará uma instância do WidgetProvider mediante solicitação

Para que o host do widget se comunique com o provedor de widget, precisamos chamar CoRegisterClassObject. Essa função exige a criação de uma implementação de IClassFactory que criará um objeto de classe para a classe WidgetProvider. Implementaremos a fábrica de classes em uma classe auxiliar autossuficiente.

No Visual Studio, clique com o botão direito do mouse no projeto ExampleWidgetProvider no Gerenciador de Soluções e selecione Adicionar –> Classe. No diálogo Adicionar classe, nomeie a classe como "FactoryHelper" e clique em Adicionar.

Substitua o conteúdo do arquivo FactoryHelper.cs pelo código a seguir. Esse código define a interface IClassFactory e implementa seus dois métodos, CreateInstance e LockServer. Esse código é o clichê típico para implementar uma fábrica de classes e não é específico da funcionalidade de um provedor de widget, exceto que indicamos que o objeto de classe que está sendo criado implementa a interface 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;

    }
}

Criar um GUID que represente o CLSID do provedor de widget

Em seguida, você precisará criar um CLSID que será usado para identificar o provedor de widget para ativação do COM. O mesmo valor também será usado ao empacotar o aplicativo. Gere um GUID no Visual Studio acessando Ferramentas –> Criar GUID. Selecione a opção de formato do registro, clique em Copiar e cole-a em um arquivo de texto para usar mais tarde.

Registrar o objeto de classe do provedor de widget no OLE

No arquivo Program.cs do executável, chamaremos CoRegisterClassObject para registrar o provedor de widget no OLE, para que o host do widget possa interagir com ele. Substitua os conteúdos do Program.cs pelo código a seguir. Esse código importa a função CoRegisterClassObject e a chama, passando a interface WidgetProviderFactory que definimos em uma etapa anterior. Atualize a declaração de variável CLSID_Factory para usar o GUID gerado na etapa anterior.

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

Observe que este exemplo de código importa a função GetConsoleWindow para determinar se o aplicativo está em execução como um aplicativo de console, o comportamento padrão deste passo a passo. Se a função retornar um ponteiro válido, gravaremos informações de depuração no console. Caso contrário, o aplicativo estará em execução como um aplicativo do Windows. Nesse caso, aguardamos o evento que definimos no método DeleteWidget quando a lista de widgets habilitados está vazia e encerramos aplicativo. Para obter informações de como converter o aplicativo de console de exemplo em um aplicativo do Windows, confira Converter o aplicativo de console em um aplicativo do Windows.

Empacotar o aplicativo de provedor de widget

Na versão atual, somente aplicativos empacotados podem ser registrados como provedores de widget. As etapas a seguir mostram o processo de empacotamento do aplicativo e atualização do manifesto do aplicativo para registrar o aplicativo no sistema operacional como um provedor de widget.

Criar um projeto de empacotamento MSIX

No Gerenciador de Soluções, clique com o botão direito do mouse na solução e selecione Adicionar –> Novo Projeto.... Na caixa de diálogo Adicionar um novo projeto, selecione o modelo "Projeto de Empacotamento de Aplicativo do Windows" e clique em Avançar. Defina o nome do projeto como "ExampleWidgetProviderPackage" e clique em Criar. Quando solicitado, defina a versão de destino como 1809 ou posterior e clique em OK. Em seguida, clique com o botão direito do mouse no projeto ExampleWidgetProviderPackage e selecione Adicionar –> Referência do projeto. Selecione o projeto ExampleWidgetProvider e clique em OK.

Adicionar a referência de pacote do SDK do Aplicativo Windows ao projeto de empacotamento

Você precisa adicionar uma referência ao pacote NuGet do SDK do Aplicativo Windows ao projeto de empacotamento MSIX. No Gerenciador de Soluções, clique duas vezes no projeto ExampleWidgetProviderPackage para abrir o arquivo ExampleWidgetProviderPackage.wapproj. Adicione o XML a seguir dentro do elemento Project.

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

Observação

Verifique se a Version especificada no elemento PackageReference corresponde à versão estável mais recente referenciada na etapa anterior.

Se a versão correta do SDK do Aplicativo Windows já estiver instalada no computador e você não quiser inserir o runtime do SDK no pacote, especifique a dependência do pacote no arquivo Package.appxmanifest do projeto 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>
...

Atualizar o manifesto do pacote

No Gerenciador de Soluções clique com o botão direito do mouse no arquivo Package.appxmanifest e selecione Exibir Código para abrir o arquivo XML de manifesto. Em seguida, você precisa adicionar algumas declarações de namespace para as extensões do pacote do aplicativo que usaremos. Adicione as seguintes definições de namespace ao elemento Package de nível superior.

<!-- Package.appmanifest -->
<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"

Dentro do elemento Application, crie um elemento vazio chamado Extensions. Ele deve estar após a marca de fechamento de uap:VisualElements.

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

    </Extensions>
</Application>

A primeira extensão que precisamos adicionar é a ComServer. Isso registra o ponto de entrada do executável no sistema operacional. Essa extensão é o aplicativo empacotado equivalente ao registro de um servidor COM por meio da definição de uma chave do Registro e não é específica para provedores de widget. Adicione o seguinte elemento com:Extension como um filho do elemento Extensions. Altere o GUID no atributo Id do elemento com:Class do GUID gerado em uma etapa anterior.

<!-- Package.appxmanifest -->
<Extensions>
    <com:Extension Category="windows.comServer">
        <com:ComServer>
            <com:ExeServer Executable="ExampleWidgetProvider\ExampleWidgetProvider.exe" DisplayName="ExampleWidgetProvider">
                <com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="ExampleWidgetProvider" />
            </com:ExeServer>
        </com:ComServer>
    </com:Extension>
</Extensions>

Em seguida, adicione a extensão que registra o aplicativo como um provedor de widget. Cole o elemento uap3:Extension no snippet de código a seguir, como um filho do elemento Extensions. Substitua o atributo ClassId do elemento COM pelo GUID usado nas etapas anteriores.

<!-- Package.appxmanifest -->
<Extensions>
    ...
    <uap3:Extension Category="windows.appExtension">
        <uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="WidgetTestApp" Id="ContosoWidgetApp" PublicFolder="Public">
            <uap3:Properties>
                <WidgetProvider>
                    <ProviderIcons>
                        <Icon Path="Images\StoreLogo.png" />
                    </ProviderIcons>
                    <Activation>
                        <!-- Apps exports COM interface which implements IWidgetProvider -->
                        <CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
                    </Activation>

                    <TrustedPackageFamilyNames>
                        <TrustedPackageFamilyName>Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe</TrustedPackageFamilyName>
                    </TrustedPackageFamilyNames>

                    <Definitions>
                        <Definition Id="Weather_Widget"
                            DisplayName="Weather Widget"
                            Description="Weather Widget Description"
                            AllowMultiple="true">
                            <Capabilities>
                                <Capability>
                                    <Size Name="small" />
                                </Capability>
                                <Capability>
                                    <Size Name="medium" />
                                </Capability>
                                <Capability>
                                    <Size Name="large" />
                                </Capability>
                            </Capabilities>
                            <ThemeResources>
                                <Icons>
                                    <Icon Path="ProviderAssets\Weather_Icon.png" />
                                </Icons>
                                <Screenshots>
                                    <Screenshot Path="ProviderAssets\Weather_Screenshot.png" DisplayAltText="For accessibility" />
                                </Screenshots>
                                <!-- DarkMode and LightMode are optional -->
                                <DarkMode />
                                <LightMode />
                            </ThemeResources>
                        </Definition>
                        <Definition Id="Counting_Widget"
                                DisplayName="Microsoft Counting Widget"
                                Description="Couting Widget Description">
                            <Capabilities>
                                <Capability>
                                    <Size Name="small" />
                                </Capability>
                            </Capabilities>
                            <ThemeResources>
                                <Icons>
                                    <Icon Path="ProviderAssets\Counting_Icon.png" />
                                </Icons>
                                <Screenshots>
                                    <Screenshot Path="ProviderAssets\Counting_Screenshot.png" DisplayAltText="For accessibility" />
                                </Screenshots>
                                <!-- DarkMode and LightMode are optional -->
                                <DarkMode>

                                </DarkMode>
                                <LightMode />
                            </ThemeResources>
                        </Definition>
                    </Definitions>
                </WidgetProvider>
            </uap3:Properties>
        </uap3:AppExtension>
    </uap3:Extension>
</Extensions>

Para obter descrições detalhadas e informações de formato de todos esses elementos, confira Formato XML do manifesto do pacote do provedor de widget.

Adicionar ícones e outras imagens ao projeto de empacotamento

No Gerenciador de Soluções, clique com o botão direito do mouse em ExampleWidgetProviderPackage e selecione Adicionar –> Nova Pasta. Nomeie essa pasta como ProviderAssets, pois esse nome foi usado no Package.appxmanifest da etapa anterior. É aqui que armazenaremos os ícones e as capturas de tela dos widgets. Depois de adicionar os ícones e as capturas de tela desejados, verifique se os nomes de imagem correspondem ao que vem após Path=ProviderAssets\ no Package.appxmanifest ou os widgets não aparecerão no host do widget.

Para obter informações sobre os requisitos de design de imagens de captura de tela e as convenções de nomenclatura de capturas de tela localizadas, confira Integrar com o seletor de widget.

Testar o provedor de widget

Verifique se você selecionou a arquitetura que corresponde ao computador de desenvolvimento na lista suspensa Plataformas de Solução, por exemplo, "x64". No Gerenciador de Soluções, clique com o botão direito do mouse na solução e selecione Compilar Solução. Depois que isso for feito, clique com o botão direito do mouse em ExampleWidgetProviderPackage e selecione Implantar. Na versão atual, o único host de widget com suporte é o Quadro de Widgets. Para ver os widgets, você precisará abrir o Quadro de Widgets e selecionar Adicionar widgets no canto superior direito. Role até a parte inferior dos widgets disponíveis e veja o Widget Clima fictício e o Widget de Contagem da Microsoft que foram criados neste tutorial. Clique nos widgets para fixá-los no quadro de widgets e testar a funcionalidade deles.

Depurar o provedor de widget

Depois de fixar os widgets, a Plataforma de Widget iniciará o aplicativo do provedor de widget para receber e enviar informações relevantes sobre o widget. Para depurar o widget em execução, você pode anexar um depurador ao aplicativo do provedor de widget em execução ou configurar o Visual Studio para iniciar automaticamente a depuração do processo do provedor de widget depois que ele for iniciado.

Para anexá-lo ao processo em execução:

  1. No Visual Studio, clique em Depurar –> Anexar ao processo.
  2. Filtre os processos e localize o aplicativo do provedor de widget desejado.
  3. Anexe o depurador.

Para anexar automaticamente o depurador ao processo quando ele for iniciado pela primeira vez:

  1. No Visual Studio, escolha Depurar –> Outros Destinos de Depuração> Depurar Pacote de Aplicativo Instalado.
  2. Filtre os pacotes e localize o pacote do provedor de widget desejado.
  3. Selecione-o e marque a caixa "Não iniciar, mas depurar o código quando ele for iniciado".
  4. Clique em Anexar.

Converter o aplicativo de console em um aplicativo do Windows

Para converter o aplicativo de console criado neste passo a passo em um aplicativo do Windows, clique com o botão direito do mouse no projeto ExampleWidgetProvider no Gerenciador de Soluções e selecione Propriedades. Em Aplicativo –> Geral, altere o Tipo de saída de "Aplicativo de Console" para "Aplicativo do Windows".

A screenshot showing the C# widget provider project properties with the output type set to Windows Application

Publicar o widget

Depois de desenvolver e testar o widget, você deve publicar o aplicativo na Microsoft Store para que os usuários instalem os widgets nos dispositivos. Para obter diretrizes passo a passo de como publicar um aplicativo, confira Publicar o aplicativo na Microsoft Store.

A coleção de repositórios de widgets

Depois que o aplicativo for publicado na Microsoft Store, você poderá solicitar que ele seja incluído na Coleção da Store de widgets que ajuda os usuários a descobrir aplicativos que apresentam Widgets do Windows. Para enviar sua solicitação, confira Enviar informações do widget para adição à Coleção da Store.

Screenshot of the Microsoft Store showing the widgets collection that allows users to discover apps that feature Windows Widgets.

Implementando a personalização do widget

A partir do SDK do Aplicativo do Windows 1.4, os widgets podem dar suporte à personalização pelo usuário. Quando esse recurso é implementado, uma opção Personalizar widget é adicionada ao menu de reticências acima da opção Desafixar widget.

A screenshot showing a widget with the customization dialog displayed.

As etapas a seguir resumem o processo de personalização do widget.

  1. Em operação normal, o provedor de widget responde às solicitações do host do widget com o modelo e os conteúdos de dados para a experiência regular do widget.
  2. O usuário clica no botão Personalizar widget no menu de reticências.
  3. O widget gera o evento OnCustomizationRequested no provedor do widget para indicar que o usuário solicitou a experiência de personalização.
  4. O provedor define um sinalizador interno para indicar que o widget está no modo de personalização. Enquanto está no modo de personalização, o provedor do widget envia os modelos JSON para a interface do usuário de personalização do widget em vez da regular.
  5. Enquanto está no modo de personalização, o provedor do widget recebe eventos OnActionInvoked à medida que o usuário interage com a interface do usuário de personalização e ajusta sua configuração e comportamento internos com base nas ações do usuário.
  6. Quando a ação associada ao evento OnActionInvoked é a ação de "personalização de saída" definida pelo aplicativo, o provedor do widget redefine seu sinalizador interno para indicar que ele não está mais no modo de personalização e retoma o envio dos modelos JSON de dados e visuais para a experiência regular do widget, refletindo as alterações solicitadas durante a personalização.
  7. O provedor do widget persiste as opções de personalização para o disco ou para a nuvem a fim de que as alterações sejam preservadas entre suas invocações.

Observação

Há um bug conhecido com o Windows Widget Board para widgets criados usando o SDK do Aplicativo do Windows, que faz com que o menu de reticências pare de responder depois que o cartão de personalização é mostrado.

Em cenários típicos de personalização do Widget, o usuário escolherá quais dados são exibidos no widget ou ajustará a apresentação visual do widget. Para simplificar, o exemplo nesta seção adicionará o comportamento de personalização que permite ao usuário redefinir o contador do widget de contagem implementado nas etapas anteriores.

Observação

A personalização do widget só tem suporte no SDK do Aplicativo do Windows 1.4 e posterior. Certifique-se de atualizar as referências em seu projeto para a última versão do pacote Nuget.

Atualizar o manifesto do pacote para declarar suporte à personalização

Para permitir que o host do widget saiba que o widget dá suporte à personalização, adicione o atributo IsCustomizable ao elemento Definição para o widget e defina-o como true.

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

Acompanhar quando um widget está no modo de personalização

O exemplo neste artigo usa o struct auxiliar CompactWidgetInfo para acompanhar o estado atual de nossos widgets ativos. Adicione o campo inCustomization, que será usado para acompanhar quando o host do widget espera que enviemos nosso modelo json de personalização em vez do modelo de widget regular.

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

Implementar IWidgetProvider2

O recurso de personalização do widget é exposto por meio da interface IWidgetProvider2. Atualize a definição da classe WidgetProvider para implementar essa interface.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2

Adicione uma declaração para o retorno de chamada OnCustomizationRequested da interface IWidgetProvider2. Esse método usa o mesmo padrão que os outros retornos de chamada que usamos. Obtemos a ID do widget a ser personalizada do WidgetContext e localizamos o struct auxiliar CompactWidgetInfo associado a esse widget e definimos o campo inCustomization como 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);
    }
}

Agora, declare uma variável de cadeia de caracteres que define o modelo JSON para a interface do usuário de personalização do widget. Para este exemplo, temos um botão "Redefinir contador" e um botão "Sair da personalização" que sinalizarão nosso provedor para retornar ao comportamento regular do widget. Coloque essa definição ao lado das outras definições de modelo.

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

Enviar modelo de personalização no UpdateWidget

Em seguida, atualizaremos nosso método auxiliar UpdateWidget que envia nossos modelos JSON de dados e visuais para o host do widget. Quando estamos atualizando o widget de contagem, enviamos o modelo de widget regular ou o modelo de personalização, dependendo do valor do campo inCustomization. Para fins de brevidade, o código não relevante para a personalização é omitido neste snippet de código.

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

Responder a ações de personalização

Quando os usuários interagem com entradas em nosso modelo de personalização, ele chama o mesmo manipulador OnActionInvoked de quando o usuário interage com a experiência regular do widget. Para dar suporte à personalização, procuramos os verbos "reset" e "exitCustomization" de nosso modelo JSON de personalização. Se a ação for para o botão "Redefinir contador", redefiniremos o contador mantido no campo customState do nosso struct auxiliar para 0. Se a ação for para o botão "Sair da personalização", definiremos o campo inCustomization como false para que, quando chamarmos UpdateWidget, nosso método auxiliar envie os modelos JSON regulares, e não o modelo de personalização.

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

Agora, ao implantar o widget, você deverá ver o botão Personalizar widget no menu de reticências. Clicar no botão Personalizar exibirá seu modelo de personalização.

A screenshot showing the widgets customization UI.

Clique no botão Redefinir contador para redefinir o contador para 0. Clique no botão Sair da personalização para retornar ao comportamento regular do widget.