Partilhar via


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

Este artigo o orienta na criação de um provedor de widget simples que implementa 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 widgets responda a uma ação do usuário em um widget. Os provedores de widgets podem suportar um único widget ou vários widgets. Neste exemplo, definiremos dois widgets diferentes. Um dos widgets é um widget de clima simulado que ilustra algumas opções de formatação fornecidas pela estrutura Adaptive Cards. O segundo widget demonstrará as ações do usuário e o recurso de estado do widget personalizado, mantendo um contador que é incrementado sempre que o usuário clica em um botão exibido no widget.

Uma captura de tela de um widget meteorológico simples. O widget mostra alguns gráficos e dados relacionados ao clima, bem como algum texto de diagnóstico ilustrando que o modelo para o widget de tamanho médio está sendo exibido.

Uma captura de tela de um widget de contagem simples. O widget mostra uma cadeia de caracteres contendo o valor numérico a ser incrementado e um botão chamado Incremento, bem como algum texto de diagnóstico ilustrando que o modelo para o widget de tamanho pequeno está sendo exibido.

Este código de exemplo neste artigo é adaptado do Windows App SDK Widgets Sample. Para implementar um provedor de widgets usando C++/WinRT, consulte Implementar um provedor de widgets em um aplicativo win32 (C++/WinRT).

Pré-requisitos

  • Seu dispositivo deve ter o modo de desenvolvedor ativado. Para obter mais informações, consulte Habilitar seu dispositivo para desenvolvimento.
  • Visual Studio 2022 ou posterior com o desenvolvimento da Plataforma Universal do Windows carga de trabalho. Certifique-se de adicionar o componente para C++ (v143) a partir da lista suspensa opcional.

Criar um novo aplicativo de console em C#

No Visual Studio, crie um novo projeto. Na caixa de diálogo Criar um novo projeto, defina o filtro de idioma como "C#" e o filtro de plataforma como Windows, depois selecione o modelo de projeto Console App. Nomeie o novo projeto como "ExampleWidgetProvider". Quando solicitado, defina a versão de destino do .NET como 8.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ãodo SO de destino , selecione a versão 10.0.19041.0 ou posterior.

Para atualizar seu projeto para oferecer suporte ao .NET 8.0, no Gerenciador de Soluções clique com o botão direito do mouse no nome do projeto e selecione Editar Arquivo de Projeto. Dentro de PropertyGroup, adicione o seguinte RuntimeIdentifiers elemento.

<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>

Note que este tutorial utiliza uma aplicação de consola que exibe a janela da consola quando o widget é ativado, a fim de facilitar a depuração. Quando estiver pronto para publicar seu aplicativo provedor de widgets, você poderá converter o aplicativo de console em um aplicativo do Windows seguindo as etapas em Converter seu aplicativo de console em um aplicativo do Windows.

Adicionar referências ao SDK de Aplicativos Windows

Este exemplo usa o pacote NuGet estável mais recente do SDK de Aplicativo Windows. No Explorador de Soluções, clique com o botão direito do rato em Dependências e selecione Gerir pacotes NuGet.... No gestor de pacotes NuGet, selecione o separador Procurar e procure por "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 no Gerenciador de Soluções e selecione de classe Add-. Na caixa de diálogo Adicionar classe, nomeie a classe "WidgetProvider" e clique em Adicionar. No arquivo de WidgetProvider.cs gerado, atualize a definição de classe para indicar que ele implementa a interface IWidgetProvider.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider

Prepare-se para rastrear widgets ativados

Um provedor de widgets pode suportar um único widget ou vários widgets. Sempre que o host do widget inicia uma operação com o provedor do widget, ele passa um ID para identificar o widget associado à operação. Cada widget também tem um nome associado e um valor de estado que pode ser usado para armazenar dados personalizados. Neste exemplo, declararemos uma estrutura auxiliar simples para armazenar a ID, o nome e os dados de cada widget fixo. Os widgets também podem estar em um estado ativo, o que é discutido na seção Ativar e Desativar, abaixo, e rastrearemos esse estado para cada widget com um valor booleano. Adicione a seguinte definição ao arquivo WidgetProvider.cs, dentro do namespace ExampleWidgetProvider, mas fora da definição de 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;

}

Na definição da classe WidgetProvider em WidgetProvider.cs, adicione um membro para o mapa que manterá a lista de widgets ativos, usando o ID do widget como a chave para 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 declarará algumas cadeias de caracteres estáticas para definir os modelos JSON para cada widget. Por conveniência, esses modelos são armazenados nas variáveis membro da classe WidgetProvider. Se precisar de um armazenamento geral para os modelos - eles podem ser incluídos como parte do pacote da aplicação: Acessando arquivos de pacote. Para obter informações sobre como criar o documento JSON do modelo de widget, consulte Criar um modelo de widget com o Adaptive Card Designer.

Na versão mais recente, os aplicativos que implementam widgets do Windows podem personalizar o cabeçalho exibido para seu widget no Painel de widgets, substituindo a apresentação padrão. Para obter mais informações, consulte Personalizar a área de cabeçalho do widget.

Observação

Na versão mais recente, os aplicativos que implementam widgets do Windows podem optar por preencher o conteúdo do widget com HTML servido a partir de uma URL especificada em vez de fornecer conteúdo no formato de esquema Adaptive Card na carga JSON passada do provedor para o Widgets Board. Os provedores de widgets ainda devem fornecer uma carga JSON do Adaptive Card, portanto, as etapas de implementação neste passo a passo são aplicáveis aos widgets da Web. Para obter mais informações, consulte provedores de widgets da Web.

// 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, implementaremos os métodos do IWidgetProvider interface. O método auxiliar UpdateWidget que é chamado em várias dessas implementações de método será mostrado posteriormente neste artigo.

Observação

Os objetos passados para os métodos de retorno de chamada da interface IWidgetProvider têm a sua validade garantida apenas dentro do retorno de chamada. Você não deve armazenar referências a esses objetos porque seu comportamento fora do contexto do retorno de chamada é indefinido.

CriarWidget

O anfitrião do widget chama CreateWidget quando o utilizador fixa um dos widgets da sua aplicação no anfitrião do widget. Primeiro, esse método obtém a ID e o nome do widget associado e adiciona uma nova instância de nossa estrutura auxiliar, CompactWidgetInfo, à coleção de widgets habilitados. Em seguida, enviamos o modelo inicial e os dados para o widget, que é encapsulado no UpdateWidget método auxiliar.

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

Eliminar Widget

O host do widget chama DeleteWidget quando o utilizador retira um dos widgets do seu aplicativo do host do widget. Quando isso ocorrer, removeremos o widget associado da nossa lista de widgets ativados para que não enviemos mais atualizações para esse 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 agora está vazia e, em caso afirmativo, definimos um evento que será usado posteriormente para permitir que o aplicativo saia quando não houver widgets habilitados. Dentro da sua definição de classe, adicione a declaração do 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 definida no modelo de widget. Para o widget de contador utilizado neste exemplo, foi declarada uma ação com um valor de verbo de "inc" no modelo JSON para o widget. O código do provedor de widget usará este valor de verbo para determinar que ação tomar em resposta à interação do utilizador.

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

No método OnActionInvoked, obtenha o valor do verbo verificando a propriedade Verb dos WidgetActionInvokedArgs passados para o método. Se o verbo é "inc", então sabemos que vamos incrementar a contagem no estado personalizado para o widget. NoWidgetActionInvokedArgs, obtenha o objeto WidgetContext e, em seguida, o WidgetId para obter a ID do widget que está a ser atualizado. Encontre a entrada em nosso mapa de widgets habilitados com a ID especificada e, em seguida, atualize o valor de estado personalizado usado para armazenar o número de incrementos. Finalmente, 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 para Adaptive Cards, consulte Action.Execute. Para obter orientação sobre como projetar interação para widgets, consulte Diretrizes de design de interação de widgets

OnWidgetContextChanged

Na versão atual, OnWidgetContextChanged só é chamado quando o usuário altera o tamanho de um widget fixo. Você pode optar por retornar um modelo/dado JSON diferente para o host do widget, dependendo do tamanho solicitado. Você também pode projetar o modelo JSON para suportar todos os tamanhos disponíveis usando renderização condicional com base no valor de host.widgetSize. Se você não precisar enviar um novo modelo ou dados para levar em conta a alteração de tamanho, poderá usar o 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);
    }
}
    

Ativar e Desativar

O método Activate é chamado para notificar o provedor de widgets de que o host do widget está atualmente interessado em receber conteúdo atualizado do provedor. Por exemplo, isso pode significar que o usuário está visualizando ativamente o host do widget. O método Deactivate é chamado para notificar o provedor de widgets 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 está mais interessado em mostrar o conteúdo mais atualizado up-to. Os provedores de widgets 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 de up-todata com preocupações com recursos, como a duração da bateria.

Ativar e Desativar são chamados por widget. Este exemplo monitoriza o estado ativo de cada widget na estrutura auxiliar CompactWidgetInfo. No método Activate, chamamos o UpdateWidget método auxiliar para atualizar nosso widget. Observe que a janela de tempo entre Ativar e Desativar pode ser pequena, pelo que é recomendável tentar fazer com que o percurso do código de atualização do widget seja o mais rápido possível.

// 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 UpdateWidget método auxiliar para atualizar um widget habilitado. Neste exemplo, verificamos o nome do widget no CompactWidgetInfo struct auxiliar passado para o método e, em seguida, definimos o modelo apropriado e JSON de dados com base em qual widget está sendo atualizado. Um WidgetUpdateRequestOptions é inicializado com o modelo, os dados e o estado personalizado do widget a ser atualizado. Chame WidgetManager::GetDefault para obter uma instância da classe WidgetManager e, em seguida, execute UpdateWidget para enviar os dados do widget atualizado ao anfitrião 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 ativados na inicialização

Quando nosso provedor de widgets é inicializado pela primeira vez, é uma boa ideia perguntar ao WidgetManager se há algum widget em execução que nosso provedor está servindo no momento. Isso ajudará a recuperar o aplicativo para o estado anterior no caso de reiniciar o computador ou o provedor falhar. Chame WidgetManager.GetDefault para obter a instância padrão do gerenciador de widgets para o aplicativo. Em seguida, chame GetWidgetInfos, que retorna uma matriz de objetos WidgetInfo . Copie os IDs, nomes e estado personalizado dos widgets para a estrutura auxiliar CompactWidgetInfo e guarde-os na variável membro RunningWidgets. Cole o código a seguir na definição de classe para a 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 instanciará o WidgetProvider mediante solicitação

Para que o host do widget se comunique com nosso provedor de widgets, devemos chamar CoRegisterClassObject. Esta função requer que criemos uma implementação do IClassFactory que criará um objeto de classe para nossa classe WidgetProvider. Vamos implementar a nossa fábrica de classes numa classe auxiliar independente.

No Visual Studio, clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione de classe Add-. Na caixa de diálogo Adicionar classe, nomeie a classe "FactoryHelper" e clique em Adicionar.

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

    }
}

Crie um GUID representando o CLSID para seu provedor de widgets

Em seguida, precisas criar um GUID representando o CLSID que será usado para identificar o teu fornecedor de widgets para ativação do COM. O mesmo valor também será usado ao empacotar seu aplicativo. Gere um GUID no Visual Studio indo para Ferramentas->Criar GUID. Selecione a opção de formato do Registro e clique em Copiar e cole-a em um arquivo de texto para que você possa copiá-lo mais tarde.

Registrar o objeto de classe do provedor de widget com OLE

No arquivo Program.cs para nosso executável, chamaremos CoRegisterClassObject para registrar nosso provedor de widgets com OLE, para que o host do widget possa interagir com ele. Substitua o conteúdo do Program.cs pelo código a seguir. Este código importa a função CoRegisterClassObject e chama-a, passando na interface WidgetProviderFactory que definimos numa etapa anterior. Certifique-se de atualizar 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á sendo executado como um aplicativo de console, o comportamento padrão para este 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 será executado como um aplicativo do Windows. Nesse caso, aguardamos o evento que definimos no método DeleteWidget quando a lista de widgets ativados estiver vazia e depois saímos da aplicação. Para obter informações sobre como converter o aplicativo de console de exemplo em um aplicativo do Windows, consulte Converter seu aplicativo de console em um aplicativo do Windows.

Empacote seu aplicativo provedor de widgets

Na versão atual, apenas aplicativos empacotados podem ser registrados como provedores de widgets. As etapas a seguir irão guiá-lo através do processo de empacotamento de seu aplicativo e atualização do manifesto do aplicativo para registrar seu aplicativo no sistema operacional como um provedor de widgets.

Criar um projeto de empacotamento MSIX

NoGerenciador de Soluções , clique com o botão direito do mouse em sua solução e selecione Adicionar-Novo Projeto.... Na caixa de diálogo Adicionar um novo projeto, selecione o modelo "Projeto de empacotamento de aplicativos 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 para a versão 1809 ou posterior e clique em OK. Em seguida, clique com o botão direito do rato no projeto ExampleWidgetProviderPackage e selecione Adicionar referência de projeto>. Selecione o projeto ExampleWidgetProvider e clique em OK.

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

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

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

Observação

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

Se a versão correta do SDK do Aplicativo Windows já estiver instalada no computador e você não quiser agrupar o tempo de execução do SDK em seu pacote, poderá especificar a dependência do pacote no arquivo Package.appxmanifest para o 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 Explorador de Soluções , clique com o botão direito do rato no ficheiro Package.appxmanifest e selecione "Ver Código" para abrir o ficheiro XML do manifesto. Em seguida, você precisa adicionar algumas declarações de namespace para as extensões de pacote de 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 novo elemento vazio chamado Extensions. Certifique-se de que isso esteja após a tag de fechamento seguinte para uap:VisualElements.

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

    </Extensions>
</Application>

A primeira extensão que precisamos adicionar é a extensão ComServer. Isso registra o ponto de entrada do executável com o sistema operacional. Esta extensão é a aplicação empacotada equivalente ao registo de um servidor COM, através da definição de uma chave de registo, e não é específica para fornecedores de widgets. Adicione o seguinte elemento com:Extension como filho do elemento Extensions. Altere o GUID no atributo Id do elemento com:Class para o GUID gerado num passo 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 widgets. Cole o elemento uap3:Extension no trecho de código a seguir, como filho do elemento Extensions. Certifique-se de substituir 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 para todos esses elementos, consulte Widget provider package manifest XML format.

Adicione ícones e outras imagens ao seu projeto de embalagem

No Explorador de Soluções , clique com o botão direito do mouse no ExampleWidgetProviderPackage e selecione Adicionar ->Nova Pasta. Nomeie essa pasta como ProviderAssets, pois é o que foi usado no Package.appxmanifest da etapa anterior. É aqui que armazenaremos os nossos ícones e capturas de tela para os nossos widgets. Depois de adicionar os ícones e capturas de tela desejados, certifique-se de que os nomes das imagens correspondam ao que vem depois Path=ProviderAssets\ em seu Package.appxmanifest ou os widgets não aparecerão no host do widget.

Para obter informações sobre os requisitos de design para capturas de tela e as convenções de nomenclatura para capturas de tela localizadas, consulte Integrar com o seletor de widgets.

Testando seu provedor de widgets

Certifique-se de ter selecionado a arquitetura que corresponde à sua máquina de desenvolvimento na lista suspensa das Plataformas de Solução, por exemplo, "x64". No Gerenciador de Soluções, clique com o botão direito do mouse em sua solução e selecione Build Solution. Depois disso, clique com o botão direito do mouse no ExampleWidgetProviderPackage e selecione Implantar. Na versão atual, o único host de widget suportado é o Widgets Board. Para ver os widgets, você precisará abrir o Painel de widgets e selecionar Adicionar widgets no canto superior direito. Role até a parte inferior dos widgets disponíveis e deverá ver o mock Widget de Clima e o Widget de Contagem da Microsoft que foram criados neste tutorial. Clique nos widgets para fixá-los ao seu painel de widgets e testar sua funcionalidade.

Depurando seu provedor de widgets

Depois de fixar seus widgets, a Plataforma de widgets iniciará seu aplicativo de provedor de widgets 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 widgets em execução ou configurar o Visual Studio para iniciar automaticamente a depuração do processo do provedor de widgets assim que ele for iniciado.

Para anexar ao processo em execução:

  1. No Visual Studio, clique em Depurar -> Anexar ao processo.
  2. Filtre os processos e encontre o aplicativo de provedor de widgets desejado.
  3. Anexe o depurador.

Para anexar o depurador automaticamente ao processo quando este for iniciado inicialmente:

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

Converter seu aplicativo de console em um aplicativo do Windows

Para converter o aplicativo de console criado neste guia passo a passo num aplicativo do Windows, clique com o botão direito do rato no projeto ExampleWidgetProvider em Gerenciador de Soluções e selecione Propriedades. Em Application-General altere o do tipo de saída de "Aplicativo de console" para "Aplicativo do Windows".

Uma captura de tela mostrando as propriedades do projeto do provedor de widgets C# com o tipo de saída definido como Windows Application

Publicando seu widget

Depois de desenvolver e testar seu widget, você pode publicar seu aplicativo na Microsoft Store para que os usuários instalem seus widgets em seus dispositivos. Para obter orientações passo a passo para publicar uma aplicação, consulte Publicar a sua aplicação na Microsoft Store.

A coleção da Widgets Store

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

Captura de ecrã da Microsoft Store a mostrar a coleção de widgets que permite aos utilizadores descobrir aplicações que apresentam Widgets do Windows.

Implementando a personalização de widgets

A partir do Windows App SDK 1.4, os widgets podem oferecer suporte à personalização do usuário. Quando este recurso é implementado, uma opção Personalizar widget é adicionada ao menu de opções acima da opção Desafixar widget.

Uma captura de tela mostrando um widget com a caixa de diálogo de personalização exibida.

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

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

Observação

Há um bug conhecido com o Windows Widget Board, para widgets criados usando o SDK de aplicativos 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 serão exibidos no widget ou ajustará a apresentação visual do widget. Para simplificar, o exemplo nesta seção adicionará um 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 de widgets só é suportada no Windows App SDK 1.4 e posterior. Certifique-se de atualizar as referências em seu projeto para a versão mais recente do pacote Nuget.

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

Para que o host do widget saiba que o widget suporta 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">
...

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

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

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

A funcionalidade de personalização do widget é exposta através da interface IWidgetProvider2. Atualize a definição de classe do WidgetProvider para implementar esta interface.

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

Adicione uma implementação para o retorno de chamada OnCustomizationRequested da interface IWidgetProvider2. Este método utiliza o mesmo padrão dos outros callbacks que temos usado. Obtemos o ID para o widget a ser personalizado a partir do WidgetContext e encontramos o CompactWidgetInfo struct auxiliar 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á ao nosso provedor para retornar ao comportamento normal 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 em UpdateWidget

Em seguida, atualizaremos nosso UpdateWidget método auxiliar que envia nossos dados e modelos JSON 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. Por uma questão de brevidade, o código não relevante para a personalização é omitido neste trecho 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 utilizadores interagem com inputs no nosso modelo de personalização, chama o mesmo manipulador OnActionInvoked como quando o utilizador interage com a experiência de widget regular. Para dar suporte à personalização, procuramos os verbos «reset» e «exitCustomization» no nosso modelo JSON de personalização. Se a ação for para o botão "Redefinir contador", redefiniremos o contador mantido no campo customState de nossa estrutura auxiliar para 0. Se a ação for para o botão "Sair da personalização", definimos o campo inCustomization como false para que, quando chamarmos UpdateWidget, nosso método auxiliar enviará 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 seu widget, verá o botão Personalizar widget no menu de três pontos. Clicar no botão personalizar exibirá seu modelo de personalização.

Uma captura de tela mostrando a interface do usuário de personalização de widgets.

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