什么是Windows 11小组件
https://learn.microsoft.com/zh-cn/windows/apps/develop/widgets/widget-service-providers
Windows小组件是小型UI容器,用于显示应用或Web服务中的文本和图形。Windows小组件使用的自适应卡片格式支持对填充小组件UI的数据进行动态绑定。若要更新小组件,应用或服务将实现一个小组件服务提供商,该提供程序响应来自小组件主机的请求,并返回指定视觉模板和小组件的关联数据的JSON字符串。
目前,可以使用打包的Win32桌面应用实现小组件提供程序。计划将来的版本支持渐进式Web应用(PWA)
实现小组件提供程序(Win32应用)
先决条件
- Windows预览体验计划(WIP)的最新开发频道Windows11生成。有关WIP自承载的详细信息,请参阅深入探讨外部测试。
- 小组件板版本521.20060.1205.0。这将附带最新的开发通道WIP版本,可以通过打开小组件板、导航到小组件选取器以及查看位于选取器右下角的版本号进行检查。
- 设备必须启用开发人员模式。有关详细信息,请参阅启用设备进行开发。
- 具有通用Windows平台开发工作负荷的VisualStudio2017或更高版本。请确保从可选下拉列表中添加C++(v143)组件。
创建新的win32控制台应用
在VisualStudio中,创建新的项目。在“创建新项目”对话框中,将语言筛选器设置为“C++”,并将平台筛选器设置为Windows,然后选择Windows控制台应用程序(C++/WinRT)项目模板。将新项目命名为“ExampleWidgetProvider”。出现提示时,将应用的目标Windows版本设置为版本1809或更高版本。
添加对Windows小组件NuGet包的引用
此示例使用最新的预览版Windows应用SDKNuGet包。在解决方案资源管理器中,右键单击“引用”并选择“管理NuGet包...”。在NuGet包管理器中,选择窗口顶部附近的“包括预发布”复选框,选择“浏览”选项卡并搜索“Microsoft.WindowsAppSDK”。在版本下拉列表中选择1.2.220930.4-preview2,然后单击“安装”。
此示例还使用Windows实现库NuGet包。在解决方案资源管理器中,右键单击“引用”并选择“管理NuGet包...”。在NuGet包管理器中,选择“浏览”选项卡并搜索“Microsoft.Windows.ImplementationLibrary”。在“版本”下拉列表中选择最新版本,然后单击“安装”。
在预编译头文件中,pch.h添加以下include指令。
//pch.h
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>
必须在任何WinRT标头之前先包含wil/cppwinrt.h标头。
添加WidgetProvider类以处理小组件操作
在VisualStudio中,右键单击ExampleWidgetProvider解决方案资源管理器中的项目,然后选择“添加类>”。在“添加类”对话框中,将类命名为“WidgetProvider”,然后单击“确定”。
声明实现IWidgetProvider接口的类
IWidgetProvider接口定义小组件主机将调用的方法,以便通过小组件提供程序启动操作。将WidgetProvider.h文件中的空类定义替换为以下代码。此代码声明实现IWidgetProvider接口的结构,并声明接口方法的原型。
// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
WidgetProvider();
/* IWidgetProvider required functions that need to be implemented */
void CreateWidget(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext WidgetContext);
void DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState);
void OnActionInvoked(winrt::Microsoft::Windows::Widgets::Providers::WidgetActionInvokedArgs actionInvokedArgs);
void OnWidgetContextChanged(winrt::Microsoft::Windows::Widgets::Providers::WidgetContextChangedArgs contextChangedArgs);
void Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
void Deactivate(winrt::hstring widgetId);
/* IWidgetProvider required functions that need to be implemented */
};
此外,添加一个专用方法UpdateWidget,它是一种帮助程序方法,用于将更新从提供程序发送到小组件主机。
// WidgetProvider.h
private:
void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);
准备跟踪已启用的小组件
小组件提供程序可以支持单个小组件或多个小组件。每当小组件主机使用小组件提供程序启动操作时,都会传递ID来标识与操作关联的小组件。每个小组件都有一个关联的名称和一个状态值,可用于存储自定义数据。在此示例中,我们将声明一个简单的帮助程序结构,用于存储每个固定小组件的ID、名称和数据。小组件也可以处于活动状态,在下面的“激活和停用”部分中讨论,我们将跟踪每个具有布尔值的小组件的状态。将以下定义添加到WidgetProvider.h文件(在WidgetProvider结构声明上方)。
// WidgetProvider.h
struct CompactWidgetInfo
{
winrt::hstring widgetId;
winrt::hstring widgetName;
int customState = 0;
bool isActive = false;
};
在WidgetProvider.h中的WidgetProvider声明中,添加将维护已启用小组件列表的映射的成员,并将小组件ID用作每个条目的键。
// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
private:
...
static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
声明小组件模板JSON字符串
此示例将声明一些静态字符串来定义每个小组件的JSON模板。为方便起见,这些模板存储在WidgetProvider类定义之外声明的局部变量中。如果需要模板的常规存储-这些模板可以包含在应用程序包中:访问包文件。有关创建小组件模板JSON文档的信息,请参阅使用自适应卡片设计器创建小组件模板。
// WidgetProvider.h
const std::string weatherWidgetTemplate = R"(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
"backgroundImage": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg",
"body": [
{
"type": "TextBlock",
"text": "Redmond, WA",
"size": "large",
"isSubtle": true,
"wrap": true
},
{
"type": "TextBlock",
"text": "Mon, Nov 4, 2019 6:21 PM",
"spacing": "none",
"wrap": true
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "Image",
"url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
"size": "small",
"altText": "Mostly cloudy weather"
}
]
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "46",
"size": "extraLarge",
"spacing": "none",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "°F",
"weight": "bolder",
"spacing": "small",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "Hi 50",
"horizontalAlignment": "left",
"wrap": true
},
{
"type": "TextBlock",
"text": "Lo 41",
"horizontalAlignment": "left",
"spacing": "none",
"wrap": true
}
]
}
]
}
]
})";
const std::string countWidgetTemplate = R"(
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "You have clicked the button ${count} times"
},
{
"text":"Rendering Only if Medium",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"medium\"}"
},
{
"text":"Rendering Only if Small",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"small\"}"
},
{
"text":"Rendering Only if Large",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"large\"}"
}
],
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
})";
实现IWidgetProvider方法
在接下来的几个部分中,我们将实现IWidgetProvider接口的方法。在本文后面的几个方法实现中调用的Helper方法UpdateWidget。在深入了解接口方法之前,请将以下行添加到WidgetProvider.cppinclude指令之后,将小组件提供程序API拉入winrt命名空间,并允许访问我们在上一步中声明的映射。
传入IWidgetProvider接口的回调方法的对象仅在回调中保证有效。不应存储对这些对象的引用,因为它们的行为不在回调上下文之外是未定义的。
// WidgetProvider.cpp
namespace winrt
{
using namespace Microsoft::Windows::Widgets::Providers;
}
std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};
CreateWidget
当用户在小组件主机中启用了某个应用的小组件时,小组件主机将调用CreateWidget。首先,此方法获取关联的小组件的ID和名称,并将帮助程序结构CompactWidgetInfo的新实例添加到已启用的小组件的集合中。接下来,我们将发送小组件的初始模板和数据,该模板和数据封装在UpdateWidget帮助程序方法中。
// WidgetProvider.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
RunningWidgets[widgetId] = runningWidgetInfo;
// Update the widget
UpdateWidget(runningWidgetInfo);
}
DeleteWidget
当用户从小组件主机取消固定应用的某个小组件时,小组件主机将调用DeleteWidget。发生这种情况时,我们将从已启用的小组件列表中删除关联的小组件,以便我们不会为该小组件发送任何进一步的更新。
// WidgetProvider.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
RunningWidgets.erase(widgetId);
}
OnActionInvoked
当用户与小组件模板中定义的操作交互时,小组件主机调用OnActionInvoked。对于此示例中使用的计数器小组件,操作在小组件的JSON模板中用谓词值“inc”声明。小组件提供程序代码将使用此谓词值来确定响应用户交互时要执行的操作。
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
在OnActionInvoked方法中,通过检查传递给该方法的WidgetActionInvokedArgs的Verb属性来获取谓词值。如果谓词为“inc”,则我们知道我们将按小组件的自定义状态递增计数。从WidgetActionInvokedArgs中,获取WidgetContext对象,然后获取WidgetId以获取要更新的小组件的ID。在已启用的小组件映射中查找具有指定ID的条目,然后更新用于存储增量数的自定义状态值。最后,使用UpdateWidget帮助程序函数使用新值更新小组件内容。
// WidgetProvider.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
auto verb = actionInvokedArgs.Verb();
if (verb == L"inc")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
}
有关自适应卡片的Action.Execute语法的信息,请参阅Action.Execute。有关设计小组件的交互的指导,请参阅小组件交互设计指南
OnWidgetContextChanged
在当前版本中,仅当用户更改固定小组件的大小时,才会调用OnWidgetContextChanged。可以选择将不同的JSON模板/数据返回到小组件主机,具体取决于所请求的大小。还可以设计模板JSON,以支持使用基于host.widgetSize值的条件呈现的所有可用大小。如果不需要发送新的模板或数据来考虑大小更改,则可以将OnWidgetContextChanged用于遥测目的。
// WidgetProvider.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
auto widgetContext = contextChangedArgs.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetSize = widgetContext.Size();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto localWidgetInfo = iter->second;
UpdateWidget(localWidgetInfo);
}
}
激活和停用
调用Activate方法以通知小组件提供程序,小组件主机当前有兴趣从提供程序接收更新的内容。例如,这可能意味着用户当前正在主动查看小组件主机。调用停用方法以通知小组件提供程序小组件主机不再请求内容更新。这两种方法定义一个窗口,其中小组件主机最感兴趣的窗口显示最新的内容。小组件提供程序可以随时向小组件发送更新,例如响应推送通知,但与任何后台任务一样,与提供最新内容与电池使用时间等资源问题保持平衡非常重要。
按小组件调用激活和停用。此示例跟踪CompactWidgetInfo帮助程序结构中每个小组件的活动状态。在Activate方法中,我们调用UpdateWidget帮助程序方法来更新小组件。请注意,“激活”和“停用”之间的时间范围可能很小,因此建议尽量尽快使小组件更新代码路径。
void WidgetProvider::Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext)
{
auto widgetId = widgetContext.Id();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = true;
UpdateWidget(localWidgetInfo);
}
}
void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = false;
}
}
更新小组件
定义UpdateWidget帮助程序方法以更新已启用的小组件。在此示例中,我们检查传递到方法的CompatWidgetInfo帮助程序结构中的小组件的名称,然后根据要更新的小组件设置相应的模板和数据JSON。WidgetUpdateRequestOptions使用要更新的小组件的模板、数据和自定义状态进行初始化。调用WidgetManager::GetDefault以获取WidgetManager类的实例,然后调用UpdateWidget将更新的小组件数据发送到小组件主机。
// WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
winrt::WidgetUpdateRequestOptions updateOptions{ localWidgetInfo.widgetId };
winrt::hstring templateJson;
if (localWidgetInfo.widgetName == L"Weather_Widget")
{
templateJson = winrt::to_hstring(weatherWidgetTemplate);
}
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
templateJson = winrt::to_hstring(countWidgetTemplate);
}
winrt::hstring dataJson;
if (localWidgetInfo.widgetName == L"Weather_Widget")
{
dataJson = L"{}";
}
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
dataJson = L"{ \"count\": " + winrt::to_hstring(localWidgetInfo.customState) + L" }";
}
updateOptions.Template(templateJson);
updateOptions.Data(dataJson);
// You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}
初始化启动时启用的小组件列表
首次初始化小组件提供程序时,最好询问WidgetManager是否存在提供程序当前提供的任何正在运行的小组件。当计算机重启或提供程序崩溃时,它将帮助将应用恢复到以前的状态。调用WidgetManager::GetDefault以获取应用的默认小组件管理器实例。然后调用GetWidgetInfos,该数组返回WidgetInfo对象的数组。将小组件ID、名称和自定义状态复制到帮助程序结构CompactWidgetInfo中,并将其保存到RunningWidgets成员变量。将以下代码粘贴到WidgetProvider类的构造函数中。
// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
for (auto widgetInfo : runningWidgets )
{
auto widgetContext = widgetInfo.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
auto customState = widgetInfo.CustomState();
if (RunningWidgets.find(widgetId) == RunningWidgets.end())
{
CompactWidgetInfo runningWidgetInfo{ 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 = std::stoi(winrt::to_string(customState));
runningWidgetInfo.customState = count;
}
catch (...)
{
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
注册将按需实例化WidgetProvider的类工厂
将定义WidgetProvider类的标头添加到应用文件顶部的main.cpp包含项。我们还将在此处包括互斥体。
// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>
接下来,需要创建一个CLSID,用于标识小组件提供程序进行COM激活。转到“工具>创建GUID”,在VisualStudio中生成GUID。选择选项“staticconstGUID=”,然后单击“复制”,然后将其粘贴到main.cpp其中。使用以下C++/WinRT语法更新GUID定义,widget_provider_clsid设置GUID变量名称。保留GUID的注释版本,因为在打包应用时需要此格式。
// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};
将以下类工厂定义添加到main.cpp。这是特定于小组件提供程序实现的样本代码。
// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
STDMETHODIMP CreateInstance(
::IUnknown* outer,
GUID const& iid,
void** result) noexcept final
{
*result = nullptr;
std::unique_lock lock(mutex);
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!instance)
{
instance = winrt::make<WidgetProvider>();
}
return instance.as(iid, result);
}
STDMETHODIMP LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
T instance{ nullptr };
std::mutex mutex;
};
int main()
{
winrt::init_apartment();
wil::unique_com_class_object_cookie widgetProviderFactory;
auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>>();
winrt::check_hresult(CoRegisterClassObject(
widget_provider_clsid,
factory.get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
widgetProviderFactory.put()));
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
打包小组件提供程序应用
在当前版本中,只能将打包的应用注册为小组件提供程序。以下步骤将引导你完成打包应用和更新应用清单的过程,以将应用注册到OS作为小组件提供程序。
创建MSIX打包项目
在解决方案资源管理器中,右键单击解决方案并选择“>添加新项目...”。在“添加新项目”对话框中,选择“Windows应用程序打包项目”模板,然后单击“下一步”。将项目名称设置为“ExampleWidgetProviderPackage”,然后单击“创建”。出现提示时,将目标版本设置为版本1809或更高版本,然后单击“确定”。接下来,右键单击ExampleWidgetProviderPackage项目,然后选择“添加>项目”引用。选择ExampleWidgetProvider项目,然后单击“确定”。
更新包清单
在解决方案资源管理器右键单击Package.appxmanifest该文件,然后选择“查看代码”以打开清单xml文件。接下来,需要为我们将使用的应用包扩展添加一些命名空间声明。将以下命名空间定义添加到顶级Package元素。
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
在Application元素内,创建名为Extensions的新空元素。请确保这在uap:VisualElements的结束标记之后。
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
我们需要添加的第一个扩展是ComServer扩展。这会向OS注册可执行文件的入口点。此扩展是打包的应用,等效于通过设置注册表项注册COM服务器,并不特定于小组件提供程序。将以下com:Extension元素添加为Extensions元素的子元素。将com:Class元素的ID属性中的GUID更改为上一步骤中生成的GUID。
<!-- Package.appxmanifest -->
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="ExampleWidgetProvider\ExampleWidgetProvider.exe" DisplayName="ExampleWidgetProvider">
<com:Class Id="80F4CB41-5758-4493-9180-4FB8D480E3F5" DisplayName="ExampleWidgetProvider" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
接下来,添加将应用注册为小组件提供程序的扩展。将uap3:Extension元素粘贴到以下代码片段中,作为Extensions元素的子元素。请务必将COM元素的ClassId属性替换为在前面的步骤中使用的GUID。
<!-- Package.appxmanifest -->
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="WidgetTestApp" Id="ContosoWidgetApp" PublicFolder="Public">
<uap3:Properties>
<WidgetProvider>
<ProviderIcons>
<Icon Path="Images\StoreLogo.png" />
</ProviderIcons>
<Activation>
<!-- Apps exports COM interface which implements IWidgetProvider -->
<CreateInstance ClassId="80F4CB41-5758-4493-9180-4FB8D480E3F5" />
</Activation>
<TrustedPackageFamilyNames>
<TrustedPackageFamilyName>Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe</TrustedPackageFamilyName>
</TrustedPackageFamilyNames>
<Definitions>
<Definition Id="Weather_Widget"
DisplayName="Weather Widget"
Description="Weather Widget Description"
AllowMultiple="true">
<Capabilities>
<Capability>
<Size Name="small" />
</Capability>
<Capability>
<Size Name="medium" />
</Capability>
<Capability>
<Size Name="large" />
</Capability>
</Capabilities>
<ThemeResources>
<Icons>
<Icon Path="ProviderAssets\Weather_Icon.png" />
</Icons>
<Screenshots>
<Screenshot Path="ProviderAssets\Weather_Screenshot.png" DisplayAltText="For accessibility" />
</Screenshots>
<!-- DarkMode and LightMode are optional -->
<DarkMode />
<LightMode />
</ThemeResources>
</Definition>
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="Couting Widget Description">
<Capabilities>
<Capability>
<Size Name="small" />
</Capability>
</Capabilities>
<ThemeResources>
<Icons>
<Icon Path="ProviderAssets\Counting_Icon.png" />
</Icons>
<Screenshots>
<Screenshot Path="ProviderAssets\Counting_Screenshot.png" DisplayAltText="For accessibility" />
</Screenshots>
<!-- DarkMode and LightMode are optional -->
<DarkMode>
</DarkMode>
<LightMode />
</ThemeResources>
</Definition>
</Definitions>
</WidgetProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
有关所有这些元素的详细说明和格式信息,请参阅小组件提供程序包清单XML格式。
向打包项目添加图标和其他图像
在解决方案资源管理器中,右键单击ExampleWidgetProviderPackage并选择“>添加新文件夹”。将此文件夹命名为ProviderAssets,因为这是上一步中使用的Package.appxmanifest内容。在这里,我们将存储小组件的图标和屏幕截图。添加所需的图标和屏幕截图后,请确保图像名称与Path=ProviderAssets\中Package.appxmanifest之后的内容匹配,否则小组件不会显示在小组件主机中。
测试小组件提供程序
在解决方案资源管理器中,右键单击解决方案并选择“生成解决方案”。完成后,右键单击ExampleWidgetProviderPackage并选择“部署”。在当前版本中,唯一受支持的小组件主机是小组件板。若要查看小组件,需要打开小组件板,然后选择右上角的“添加小组件”。滚动到可用小组件底部,应会看到本教程中创建的模拟天气小组件和Microsoft计数小组件。单击小组件将其固定到小组件板并测试其功能。