Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Ez a cikk bemutatja, hogyan hozhat létre egy egyszerű widget-szolgáltatót, amely megvalósítja az IWidgetProvider felületet. Ennek a felületnek a metódusait a widget-gazdagép meghívja, hogy kérje le a widgetet meghatározó adatokat, vagy hogy a widgetszolgáltató válaszoljon egy widget felhasználói műveletére. A widgetszolgáltatók egyetlen vagy több widgetet is támogathatnak. Ebben a példában két különböző widgetet határozunk meg. Az egyik widget egy szimulált időjárási widget, amely az Adaptív kártyák keretrendszer által biztosított formázási lehetőségek némelyikét mutatja be. A második widget bemutatja a felhasználói műveleteket és az egyéni widgetállapot-funkciót egy olyan számláló fenntartásával, amely növekszik, amikor a felhasználó a widgeten megjelenő gombra kattint.
A cikkben szereplő mintakód a Windows App SDK widgetek mintáiból lett átdolgozva. A widget-szolgáltató C# használatával történő implementálásához lásd: Widget-szolgáltató implementálása win32-alkalmazásokban (C#).
Előfeltételek
- Az eszköznek engedélyeznie kell a fejlesztői módot. További információ: Beállítások fejlesztőknek.
- Visual Studio 2022 vagy újabb verzió a Univerzális Windows Platform fejlesztési munkafolyamattal. Mindenképpen adja hozzá a C++ (v143) összetevőt az opcionális legördülő listából.
Új C++/WinRT win32-konzolalkalmazás létrehozása
Hozzon létre egy új projektet a Visual Studióban. Az Új projekt létrehozása párbeszédpanelen állítsa a nyelvi szűrőt "C++" értékre, a platformszűrőt pedig Windowsra, majd válassza ki a Windows Konzolalkalmazás (C++/WinRT) projektsablont. Nevezze el az új projektet "ExampleWidgetProvider" néven. Amikor a rendszer kéri, állítsa az alkalmazás cél Windows-verzióját az 1809-es vagy újabb verzióra.
Hivatkozások hozzáadása a Windows App SDK-hoz és a Windows implementációs könyvtár nuGet-csomagjaihoz
Ez a minta a legújabb stabil Windows App SDK NuGet-csomagot használja. A Megoldáskezelőben kattintson a jobb gombbal a Hivatkozások elemre , és válassza a NuGet-csomagok kezelése... lehetőséget. A NuGet-csomagkezelőben válassza a Tallózás lapot, és keressen rá a "Microsoft.WindowsAppSDK" kifejezésre. Válassza ki a legújabb stabil verziót a Verzió legördülő listában, majd kattintson a Telepítés gombra.
Ez a minta a Windows implementációs kódtár NuGet-csomagjának használatát is használja. A Megoldáskezelőben kattintson a jobb gombbal a Hivatkozások elemre , és válassza a NuGet-csomagok kezelése... lehetőséget. A NuGet-csomagkezelőben válassza a Tallózás lapot, és keressen rá a "Microsoft.Windows.ImplementationLibrary" kifejezésre. Válassza ki a legújabb verziót a Verzió legördülő listában, majd kattintson a Telepítés gombra.
Az előre összeállított fejlécfájlban (pch.h) adja hozzá az alábbi include utasításokat.
//pch.h
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>
Megjegyzés:
Először a wil/cppwinrt.h fejlécet kell tartalmaznia a WinRT-fejlécek előtt.
A widget-szolgáltató alkalmazás megfelelő leállításának kezeléséhez a winrt::get_module_lock egyéni implementációjára van szükség. Előre deklaráljuk a SignalLocalServerShutdown metódust, amely a main.cpp fájlban lesz definiálva, és beállítunk egy eseményt, amely az alkalmazás kilépését jelzi. Adja hozzá a következő kódot a pch.h fájlhoz, közvetlenül a #pragma once irányelv alá, a többi beillesztés előtt.
//pch.h
#include <stdint.h>
#include <combaseapi.h>
// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();
namespace winrt
{
inline auto get_module_lock() noexcept
{
struct service_lock
{
uint32_t operator++() noexcept
{
return ::CoAddRefServerProcess();
}
uint32_t operator--() noexcept
{
const auto ref = ::CoReleaseServerProcess();
if (ref == 0)
{
SignalLocalServerShutdown();
}
return ref;
}
};
return service_lock{};
}
}
#define WINRT_CUSTOM_MODULE_LOCK
WidgetProvider-osztály hozzáadása a widgetműveletek kezeléséhez
A Visual Studio-ban kattintson a jobb gombbal a ExampleWidgetProvider projektre a Megoldáskezelőben, és válassza a Hozzáadás ->Osztálylehetőséget. Az Osztály hozzáadása párbeszédpanelen nevezze el a "WidgetProvider" osztályt, majd kattintson a hozzáadása elemre.
Az IWidgetProvider interfészt implementáló osztály deklarálása
Az IWidgetProvider felület olyan metódusokat határoz meg, amelyeket a widget-gazdagép meghív a vezérlőszolgáltatóval való műveletek kezdeményezéséhez. Cserélje le a WidgetProvider.h fájl üres osztálydefinícióját a következő kódra. Ez a kód deklarál egy struktúrát, amely implementálja az IWidgetProvider interfészt, és deklarálja a prototípusokat az interfész módszereihez.
// 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 */
};
Emellett adjon hozzá egy privát metódust, az UpdateWidgetet, amely egy segédmetódus, amely frissítéseket küld a szolgáltatótól a widget-gazdagépnek.
// WidgetProvider.h
private:
void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);
Felkészülés az engedélyezett widgetek nyomon követésére
A widget-szolgáltatók egyetlen vagy több widgetet is támogathatnak. Amikor a widget-gazdagép műveletet kezdeményez a widget-szolgáltatónál, egy azonosítót ad át a művelethez társított widget azonosításához. Minden vezérlőhöz tartozik egy társított név és egy állapotérték is, amely egyéni adatok tárolására használható. Ebben a példában egy egyszerű segédstruktúrát deklarálunk az egyes rögzített vezérlők azonosítójának, nevének és adatainak tárolásához. A widgetek aktív állapotban is lehetnek, amelyet az alábbi Aktiválás és inaktiválás szakaszban ismertetünk, és ezt az állapotot minden widget esetében logikai értékkel követjük nyomon. Adja hozzá a következő definíciót a WidgetProvider.h fájlhoz a WidgetProvider struct deklarációja fölött.
// WidgetProvider.h
struct CompactWidgetInfo
{
winrt::hstring widgetId;
winrt::hstring widgetName;
int customState = 0;
bool isActive = false;
};
A WidgetProvider.h widgetProvider deklarációjában adjon hozzá egy tagot a térképhez, amely megőrzi az engedélyezett widgetek listáját, és minden bejegyzéshez kulcsként használja a widget azonosítóját.
// WidgetProvider.h
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
private:
...
static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
Widgetsablon JSON-sztringjeinek deklarálása
Ez a példa néhány statikus sztringet deklarál az egyes vezérlők JSON-sablonjainak meghatározásához. A kényelem érdekében ezek a sablonok a WidgetProvider osztálydefiníción kívül deklarált helyi változókban vannak tárolva. Ha általános tárterületre van szüksége a sablonokhoz , az alkalmazáscsomag részeként is felvehetők: Csomagfájlok elérése. A widgetsablon JSON-dokumentumának létrehozásáról további információt az Adaptív kártyatervezővel rendelkező widgetsablon létrehozása című témakörben talál.
A legújabb kiadásban a Windows-vezérlőket implementáló alkalmazások testre szabhatják a widgethez megjelenített fejlécet a Widgets Boardban, felülírva az alapértelmezett bemutatót. További információ: A widget fejlécterületének testreszabása.
Megjegyzés:
A legújabb kiadásban a Windows-widgeteket implementáló alkalmazások dönthetnek úgy, hogy a widget tartalmát egy megadott URL-címről kiszolgált HTML-lel töltik fel ahelyett, hogy a szolgáltatótól a Widgets Boardnak átadott JSON-hasznos adatban adja meg az Adaptív kártya sémaformátumában lévő tartalmat. A vezérlőszolgáltatóknak továbbra is rendelkezniük kell egy adaptív kártya JSON-hasznos adatával, így az útmutató implementálási lépései a webes widgetekre vonatkoznak. További információ: Webes widget-szolgáltatók.
// 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"
})";
Az IWidgetProvider metódusok implementálása
A következő néhány szakaszban implementáljuk az IWidgetProvider felület módszereit. A cikk későbbi részében megjelenik az a UpdateWidget segítő metódus, amelyet több ilyen metódusmegvalósításban is meghívnak. Mielőtt belemerülne az interfész metódusokba, adja hozzá a következő sorokat a WidgetProvider.cpp-hoz, az include irányelvek után, hogy beolvassa a widget-szolgáltató API-kat a winrt névtérbe, és hozzáférést biztosítson az előző lépésben deklarált térképhez.
Megjegyzés:
Az IWidgetProvider felület visszahívási metódusaiba átadott objektumok csak a visszahíváson belül lesznek érvényesek. Ne tároljon hivatkozásokat ezekre az objektumokra, mert a visszahívás környezetén kívüli viselkedésük nincs meghatározva.
// WidgetProvider.cpp
namespace winrt
{
using namespace Microsoft::Windows::Widgets::Providers;
}
std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};
CreateWidget
A widget-gazdagép meghívja a CreateWidgetet , amikor a felhasználó az alkalmazás egyik widgetjét rögzítette a widget-gazdagépen. Először is ez a módszer lekéri a társított widget azonosítóját és nevét, és hozzáad egy új példányt a compactWidgetInfo segédstruktúránkhoz az engedélyezett widgetek gyűjteményéhez. Ezután elküldjük a widget kezdeti sablonját és adatait, amely az UpdateWidget segédmetódusban van beágyazva.
// 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);
}
Widget törlése
A widget-gazdagép akkor hívja meg a DeleteWidgetet , ha a felhasználó törölte az alkalmazás egyik widgetét a widget-gazdagépről. Ha ez történik, eltávolítjuk a társított widgetet az engedélyezett widgetek listájából, hogy ne küldhessünk további frissítéseket a widgethez.
// WidgetProvider.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
RunningWidgets.erase(widgetId);
}
OnActionInvoked
A widget-gazdagép meghívja az OnActionInvoked parancsot , amikor a felhasználó egy, a widgetsablonban meghatározott művelettel lép kapcsolatba. Az ebben a példában használt számláló widget esetében a widget JSON-sablonjában egy "inc" értékű műveletet deklaráltak. A widget-szolgáltató kódja ezt az igeértéket használja annak meghatározására, hogy milyen műveletet kell elvégezni a felhasználói beavatkozásra válaszul.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
Az OnActionInvoked metódusban szerezze meg az ige értékét a WidgetActionInvokedArgs objektum Verb tulajdonságának ellenőrzésével. Ha az ige "inc", akkor tudjuk, hogy a widget testreszabott állapotában növelni fogjuk a számlálót. A WidgetActionInvokedArgs fájlból kérje le a WidgetContext objektumot, majd a WidgetId azonosítót a frissített widget azonosítójának lekéréséhez. Keresse meg a bejegyzést az engedélyezett widgetek térképén a megadott azonosítóval, majd frissítse a növekmények tárolásához használt egyéni állapotértéket. Végül frissítse a widget tartalmát az új értékkel az UpdateWidget segédfüggvénnyel.
// 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);
}
}
}
Az Adaptív kártyák Action.Execute szintaxisával kapcsolatos információkért lásd: Action.Execute. A widgetek interakciójának tervezésével kapcsolatos útmutatásért tekintse meg vezérlők interakciójának tervezési útmutatóját
OnWidgetContextChanged
Az aktuális kiadásban az OnWidgetContextChanged csak akkor lesz meghívva, ha a felhasználó módosítja a rögzített widget méretét. A kért mérettől függően másik JSON-sablont/adatot adhat vissza a widget-gazdagépnek. A JSON sablont úgy is megtervezheti, hogy a host.widgetSize értékétől függően feltételes rendereléssel támogassa az összes elérhető méretet. Ha nem kell új sablont vagy adatokat küldenie a méretváltozáshoz, az OnWidgetContextChanged telemetriai célokra használható.
// 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);
}
}
Aktiválás és inaktiválás
Az Activate metódust meghívva értesítheti a widget-szolgáltatót arról, hogy a widget-gazdagép jelenleg szeretne frissített tartalmat kapni a szolgáltatótól. Ez például azt jelentheti, hogy a felhasználó jelenleg aktívan tekinti meg a widget-gazdagépet. A rendszer meghívja az Inaktiválás metódust, amely értesíti a widget-szolgáltatót arról, hogy a widget-gazdagép már nem kér tartalomfrissítéseket. Ez a két módszer határoz meg egy ablakot, amelyben a widget-gazdagép leginkább a up-to-date tartalmat szeretné ábrázolni. A widget-szolgáltatók bármikor küldhetnek frissítéseket a widgetnek, például leküldéses értesítésre válaszolva, de mint minden háttérfeladat esetében, fontos, hogy up-todátumtartalmat biztosítson az erőforrásokkal kapcsolatos problémákkal, például az akkumulátor üzemidejével.
aktiválása és Inaktiválás meghívása widgetenként történik. Ez a példa a CompactWidgetInfo segédstruktúra egyes vezérlőinek aktív állapotát követi nyomon. Az Aktiválás metódusban meghívjuk az UpdateWidget segédmetódust a widget frissítéséhez. Vegye figyelembe, hogy az aktiválás és az inaktiválás közötti időablak kicsi lehet, ezért javasoljuk, hogy próbálja meg a lehető leggyorsabban frissíteni a widget kódját.
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;
}
}
Widget frissítése
Adja meg az UpdateWidget segédmetódusát egy engedélyezett widget frissítéséhez. Ebben a példában ellenőrizzük a CompactWidgetInfo segédstruktúrában átadott widget nevét, majd beállítjuk a megfelelő sablont és annak adat JSON-ját, annak függvényében, hogy melyik widgetet frissítjük. A WidgetUpdateRequestOptions inicializálva van a frissített widget sablonjával, adataival és egyéni állapotával. Hívja meg a WidgetManager::GetDefault parancsot a WidgetManager osztály egy példányának lekéréséhez, majd az UpdateWidget hívásával küldje el a frissített widgetadatokat a widget-gazdagépnek.
// 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);
}
Az engedélyezett widgetek listájának inicializálása indításkor
A widget-szolgáltató első inicializálásakor érdemes megkérdezni a WidgetManagert , hogy vannak-e futó widgetek, amelyeket a szolgáltató jelenleg kiszolgál. Segít helyreállítani az alkalmazást az előző állapotba a számítógép újraindítása vagy a szolgáltató összeomlása esetén. Hívja meg a WidgetManager::GetDefault parancsot az alkalmazás alapértelmezett widget-kezelő példányának lekéréséhez. Ezután hívja meg a GetWidgetInfos parancsot, amely WidgetInfo-objektumok tömbét adja vissza. Másolja át a widget ID-ket, neveket és egyéni állapotokat a segítő struktúrába CompactWidgetInfo, és mentse el a RunningWidgets tagváltozóba. Illessze be a következő kódot a WidgetProvider osztályának konstruktorába.
// 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{ widgetId, widgetName };
try
{
// If we had any save state (in this case we might have some state saved for Counting widget)
// convert string to required type if needed.
int count = std::stoi(winrt::to_string(customState));
runningWidgetInfo.customState = count;
}
catch (...)
{
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
Regisztráljon egy osztály-előállítót, amely kérésre példányosítani fogja a WidgetProvidert
Adja hozzá a WidgetProvider osztályt meghatározó fejlécet az alkalmazás main.cpp fájljának tetején található belek közé. Itt is be fogjuk vonni a mutex-et.
// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>
Deklarálja azt az eseményt, amely az alkalmazás kilépését eredményezi, valamint a SignalLocalServerShutdown függvényt, amely beállítja az eseményt. Illessze be a következő kódot a main.cpp.
// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);
void SignalLocalServerShutdown()
{
g_shudownEvent.SetEvent();
}
Ezután létre kell hoznia egy CLSID- kódot, amely a COM-aktiválás widgetszolgáltatójának azonosítására szolgál. Visual Studióban generáljon GUID-azonosítót úgy, hogy lép a Eszközök ->GUID létrehozásamenüpontra. Válassza a "static const GUID =" lehetőséget, kattintson a Másolás gombra, majd illessze be a main.cpp. Frissítse a GUID-definíciót a következő C++/WinRT szintaxissal, és állítsa be a GUID változó nevét widget_provider_clsid. Hagyja meg a GUID megjegyzésként megadott verzióját, mert később szüksége lesz erre a formátumra az alkalmazás csomagolásakor.
// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};
Adja hozzá a következő osztálygyári meghatározást a main.cpp-hoz. Ez többnyire olyan sablonkód, amely nem a widget-szolgáltató implementációira vonatkozik. Vegye figyelembe, hogy CoWaitForMultipleObjects megvárja a leállítási esemény aktiválását, mielőtt az alkalmazás kilépne.
// 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()));
DWORD index{};
HANDLE events[] = { g_shudownEvent.get() };
winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
INFINITE,
static_cast<ULONG>(std::size(events)), events, &index));
return 0;
}
Csomagolja a widget-szolgáltató alkalmazást
Az aktuális kiadásban csak a csomagolt alkalmazások regisztrálhatók widget-szolgáltatókként. Az alábbi lépések végigvezetik az alkalmazás csomagolásának és az alkalmazásjegyzék frissítésének folyamatán, hogy regisztrálja az alkalmazást az operációs rendszerben widget-szolgáltatóként.
MSIX csomagolási projekt létrehozása
A Megoldáskezelőben kattintson a jobb gombbal a megoldásra, és válassza az Add-New> Project(Új projekt hozzáadása) lehetőséget. Az Új projekt hozzáadása párbeszédpanelen válassza a "Windows Alkalmazáscsomagoló projekt" sablont, és kattintson a Tovább gombra. Állítsa a projekt nevét "ExampleWidgetProviderPackage" értékre, majd kattintson a létrehozása gombra. Amikor a rendszer kéri, állítsa a célverziót az 1809-es vagy újabb verzióra, és kattintson az OK gombra. Ezután kattintson a jobb egérgombbal az ExampleWidgetProviderPackage projektre, és válassza a Hozzáadás>Projekthivatkozáslehetőséget. Válassza ki a ExampleWidgetProvider projektet, és kattintson az OK gombra.
Windows App SDK-csomaghivatkozás hozzáadása a csomagolási projekthez
Az MSIX csomagolási projekthez hozzá kell adnia egy hivatkozást a Windows App SDK nuget-csomaghoz. A Megoldáskezelőablakban kattintson duplán az ExampleWidgetProviderPackage projektre az ExampleWidgetProviderPackage.wapproj fájl megnyitásához. Adja hozzá a következő xml-t a Project elemhez.
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
Megjegyzés:
Győződjön meg arról, hogy a PackageReference elemben megadott verzió megegyezik az előző lépésben hivatkozott legújabb stabil verzióval.
Ha a Windows App SDK megfelelő verziója már telepítve van a számítógépen, és nem szeretné csomagolni az SDK-futtatókörnyezetet a csomagban, a csomagfüggőséget a ExampleWidgetProviderPackage projekt Package.appxmanifest fájljában adhatja meg.
<!--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>
...
A csomagjegyzék frissítése
A Megoldáskezelőben kattintson a jobb gombbal a fájlra, Package.appxmanifest és válassza a Kód megtekintése lehetőséget a jegyzék xml-fájljának megnyitásához. Ezután hozzá kell adnia néhány névtér-deklarációt a használni kívánt alkalmazáscsomag-bővítményekhez. Adja hozzá a következő névtérdefiníciókat a legfelső szintű csomagelemhez .
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
Az Alkalmazáselemen belül hozzon létre egy új üres, Bővítmények nevű elemet. Győződjön meg arról, hogy ez a uap:VisualElementszáró címkéje után jelenik meg.
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
Az első bővítmény, amelyet hozzá kell adnunk, a ComServer bővítmény. Ez regisztrálja a végrehajtható fájl belépési pontját az operációs rendszeren. Ez a bővítmény a COM-kiszolgáló regisztrációs kulcs beállításával történő regisztrálásával egyenértékű csomagolt alkalmazás, és nem a widget-szolgáltatókra vonatkozik. Adja hozzá a következő com:Extension elemet a Bővítmények elem gyermekelemeként. Módosítsa a Id attribútum GUID-ját a com:Class elemben arra a GUID-ra, amelyet az előző lépésben generált.
<!-- 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>
Ezután adja hozzá az alkalmazást widget-szolgáltatóként regisztráló bővítményt. Illessze be a uap3:Extension elemet a következő kódrészletbe, a Bővítmények elem gyermekeként. Mindenképpen cserélje le a COM-elemClassId attribútumát az előző lépésekben használt GUID azonosítóra.
<!-- 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>
Az összes elem részletes leírását és formátuminformációit lásd itt: Widget provider package manifest XML format.
Ikonok és egyéb képek hozzáadása a csomagolási projekthez
A Megoldáskezelőbenkattintson a jobb gombbal az ExampleWidgetProviderPackage elemre, majd válassza a Hozzáadás –>Új mappaparancsot. Nevezze el ezt a mappát ProviderAssets néven, mivel ez volt az Package.appxmanifest előző lépésben használt mappa. Itt fogjuk tárolni a widgetek ikonjait és képernyőképeit . Miután hozzáadta a kívánt ikonokat és képernyőképeket, győződjön meg arról, hogy a képnevek megegyeznek a Path=ProviderAssets\ után található névvel a Package.appxmanifest-ben, különben a widgetek nem fognak megjelenni a widgetgazdagépen.
A képernyőképképek tervezési követelményeivel és a honosított képernyőképek elnevezési konvencióival kapcsolatos információkért lásd: Integrálás a vezérlőválasztóval.
A widget-szolgáltató tesztelése
Győződjön meg arról, hogy kiválasztotta a fejlesztői gépnek megfelelő architektúrát a Megoldásplatformok legördülő listából, például "x64". A Megoldáskezelőben kattintson a jobb gombbal a megoldásra, és válassza a Megoldás létrehozása lehetőséget. Ha ez megtörtént, kattintson jobb gombbal az ExampleWidgetProviderPackage elemre, és válassza a Üzembe helyezéslehetőséget. Az aktuális kiadásban az egyetlen támogatott widget-gazdagép a Widgets Board. A widgetek megtekintéséhez meg kell nyitnia a widgetek tábláját, és a jobb felső sarokban válassza a Widgetek hozzáadása lehetőséget. Görgessen az elérhető widgetek aljára, és látnia kell az oktatóanyagban létrehozott, a példaként szolgáló Weather Widget és Microsoft Counting Widget widgeteket. A widgetekre kattintva rögzítheti őket a widgetek tábláján, és tesztelheti a funkciójukat.
A widget-szolgáltató hibakeresése
Miután rögzítette a widgeteket, a widgetplatform elindítja a widget-szolgáltató alkalmazást, hogy releváns információkat kapjon és küldjön a widgetről. A futó widget hibakereséséhez csatolhat egy hibakeresőt a futó widget-szolgáltató alkalmazáshoz, vagy beállíthatja, hogy a Visual Studio automatikusan elindítsa a widget-szolgáltatói folyamat hibakeresését az indítás után.
A futó folyamathoz való csatoláshoz:
- A Visual Studióban kattintson a(z) Hibakeresés -> Csatlakozásfolyamathoz.
- Szűrje a folyamatokat, és keresse meg a kívánt widgetszolgáltatói alkalmazást.
- Csatold a hibakeresőt.
Annak érdekében, hogy a hibakereső automatikusan csatolva legyen a folyamathoz az első indításkor:
- A Visual Studióban kattintson a Hibakeresés –> Egyéb hibakeresési célok –> Telepített alkalmazáscsomag hibakeresése elemre.
- Szűrje a csomagokat, és keresse meg a kívánt widget-szolgáltatói csomagot.
- Válassza ki, és jelölje be a jelölőnégyzetet, amelynek szövege: "Ne induljon el, de végezzen hibakeresést, amikor a kód elindul".
- Kattintson az ésCsatolás gombra.
A konzolalkalmazás átalakítása Windows-alkalmazássá
Az ebben az útmutatóban létrehozott konzolalkalmazás windowsos alkalmazássá alakításához:
- Kattintson a jobb gombbal a ExampleWidgetProvider projektre a Megoldáskezelőben , és válassza a Tulajdonságok lehetőséget. Lépjen a Linker -> Rendszer részre, és módosítsa a AlsóRendszert "Konzol" értékről "Windows" értékre. Ez úgy is elvégezhető, hogy hozzáadjuk a <SubSystem>Windows</SubSystem>-t a <Link>..</Link> szakaszába a .vcxproj fájlban.
- A(z) main.cpp-ban módosítsa a
int main()értéketint WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/)-re.
A widget közzététele
Miután kifejlesztette és tesztelte a widgetet, közzéteheti az alkalmazást a Microsoft Store-ban, hogy a felhasználók telepíthessék a widgeteket az eszközeikre. Az alkalmazások közzétételével kapcsolatos részletes útmutatásért lásd : Alkalmazás közzététele a Microsoft Store-ban.
A widgetek áruházgyűjteménye
Miután az alkalmazást közzétette a Microsoft Store-ban, kérheti, hogy az alkalmazás szerepeljen a widgetek Áruház gyűjteményében, amely segít a felhasználóknak felderíteni a Windows widgeteket tartalmazó alkalmazásokat. A kérés elküldéséhez lásd: a widget adatainak benyújtása a Store gyűjteményhez való hozzáadáshoz.
Widget testreszabásának megvalósítása
A Windows App SDK 1.4-től kezdve a widgetek támogatják a felhasználók testreszabását. A funkció megvalósításakor a rendszer hozzáad egy testreszabási widget-beállítást a vezérlő rögzítésének feloldása lehetőség feletti három pont menühöz.
Az alábbi lépések összefoglalják a widget testreszabásának folyamatát.
- Normál működés esetén a widget-szolgáltató válaszol a widget-gazdagéptől érkező kérésekre a rendszeres widgetélményhez tartozó vizualizációs és adat JSON-sablonokkal.
- A felhasználó az ellipszis menüben a Widget testreszabása gombra kattint.
- A widget az OnCustomizationRequested eseményt a widget-szolgáltatón jelzi, hogy a felhasználó kérte a widget testreszabási felületét.
- A widget-szolgáltató beállít egy belső jelzőt, amely jelzi, hogy a widget testreszabási módban van. Testreszabási módban a widget-szolgáltató a szokásos widget-felhasználói felület helyett elküldi a widget testreszabási felhasználói felületéhez tartozó JSON-sablonokat.
- Testreszabási módban a widget-szolgáltató onActionInvoked eseményeket fogad, miközben a felhasználó interakcióba lép a testreszabási felhasználói felülettel, és a felhasználó műveletei alapján módosítja annak belső konfigurációját és viselkedését.
- Ha az OnActionInvoked eseményhez társított művelet az alkalmazás által definiált "kilépés testreszabása" művelet, a widget-szolgáltató visszaállítja a belső jelzőt, hogy jelezze, hogy már nincs testreszabási módban, és folytatja a vizualizációs és adat JSON-sablonok küldését a szokásos widget-élmény érdekében, a testreszabás során kért módosításokat tükrözve.
- A widget-szolgáltató megőrzi a lemez vagy a felhő testreszabási beállításait, hogy a módosítások megmaradjanak a widget-szolgáltató meghívásai között.
Megjegyzés:
A Windows Widget Board ismert hibája a Windows App SDK-val létrehozott vezérlők esetében, amely miatt a három pont menü nem válaszol a testreszabási kártya megjelenítése után.
A widgetek tipikus testreszabási forgatókönyveiben a felhasználó kiválaszthatja, hogy milyen adatok jelenjenek meg a widgeten, vagy módosíthatja a widget vizuális megjelenítését. Az egyszerűség kedvéért az ebben a szakaszban szereplő példa olyan testreszabási viselkedést ad hozzá, amely lehetővé teszi, hogy a felhasználó visszaállítsa az előző lépésekben implementált számláló widget számlálóját.
Megjegyzés:
A widget testreszabása csak a Windows App SDK 1.4-ben és újabb verzióiban támogatott. Győződjön meg arról, hogy a projekt hivatkozásait a Nuget-csomag legújabb verziójára frissíti.
A csomagjegyzék frissítése a testreszabási támogatás deklarálásához
Ha tudatni szeretné a widget-gazdagéppel, hogy a widget támogatja a testreszabást, adja hozzá az IsCustomizable attribútumot a widget definícióelőtagjához, és állítsa igaz értékre.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
WidgetProvider.h frissítése
Ha testre szabási támogatást szeretne adni a jelen cikk előző lépéseiben létrehozott widgethez, frissítenie kell a widgetszolgáltató, a WidgetProvider.h fejlécfájlt.
Először frissítse a CompactWidgetInfo definíciót. Ez a segédstruktúra segít nyomon követni az aktív widgetek aktuális állapotát. Adja hozzá az inCustomization mezőt, amely nyomon követi, hogy a widget-gazdagép mikor várja, hogy elküldjük a testreszabási JSON-sablont a szokásos widgetsablon helyett.
// WidgetProvider.h
struct CompactWidgetInfo
{
winrt::hstring widgetId;
winrt::hstring widgetName;
int customState = 0;
bool isActive = false;
bool inCustomization = false;
};
Frissítse a WidgetProvider deklarációt az IWidgetProvider2 felület implementálásához.
// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider2>
Adjon hozzá egy deklarációt az IWidgetProvider2 felület OnCustomizationRequested visszahívásához.
// WidgetProvider.h
void OnCustomizationRequested(winrt::Microsoft::Windows::Widgets::Providers::WidgetCustomizationRequestedArgs args);
Végül deklaráljon egy sztringváltozót, amely meghatározza a widget testreszabási felhasználói felületéhez tartozó JSON-sablont. Ebben a példában az "Alaphelyzetbe állítás számláló" gomb és a "Kilépés a testreszabásból" gomb jelzi a szolgáltatónknak, hogy térjen vissza a szokásos widget-működéshez.
// WidgetProvider.h
const std::string countWidgetCustomizationTemplate = R"(
{
"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"
})";
WidgetProvider.cpp frissítése
Most frissítse a WidgetProvider.cpp fájlt a widget testreszabási viselkedésének implementálásához. Ez a módszer ugyanazt a mintát használja, mint a többi használt visszahívás. Lekérjük a widget testreszabásához szükséges azonosítót a WidgetContextből , és megkeresjük a widgethez társított CompactWidgetInfo segédszerkezetet, és igaz értékre állítjuk az inCustomization mezőt.
//WidgetProvider.cpp
void WidgetProvider::OnCustomizationRequested(winrt::WidgetCustomizationRequestedArgs args)
{
auto widgetId = args.WidgetContext().Id();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.inCustomization = true;
UpdateWidget(localWidgetInfo);
}
}
Ezután frissítjük az UpdateWidget segédmetódusunkat, amely elküldi az adatokat és a vizuális JSON-sablonokat a widget-gazdagépnek. A számláló widget frissítésekor a szokásos widgetsablont vagy a testreszabási sablont küldjük el az inCustomization mező értékétől függően. A rövidség kedvéért a testreszabás szempontjából nem releváns kód hiányzik ebben a kódrészletben.
//WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
...
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
if (!localWidgetInfo.inCustomization)
{
std::wcout << L" - not in customization " << std::endl;
templateJson = winrt::to_hstring(countWidgetTemplate);
}
else
{
std::wcout << L" - in customization " << std::endl;
templateJson = winrt::to_hstring(countWidgetCustomizationTemplate);
}
}
...
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);
}
Amikor a felhasználók a testreszabási sablonban lévő bemenetekkel dolgoznak, ugyanazt az OnActionInvoked kezelőt hívja meg, mint amikor a felhasználó a szokásos vezérlőélményt használja. A testreszabás támogatása érdekében a testreszabási JSON-sablonban az "reset" és "exitCustomization" igéket keressük. Ha a művelet a "Számláló alaphelyzetbe állítása" gombra van állítva, a segédstruktúra customState mezőjében tárolt számlálót 0 értékre állítjuk vissza. Ha a művelet a "Kilépés a testreszabásból" gombra van állítva, az InCustomization mezőt hamisra állítjuk, hogy az UpdateWidget hívásakor a segédmetódus a szokásos JSON-sablonokat küldje el, és ne a testreszabási sablont.
//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);
}
}
else if (verb == L"reset")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Reset the count
localWidgetInfo.customState = 0;
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == L"exitCustomization")
{
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;
// Stop sending the customization template
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
}
Most, amikor üzembe helyezi a widgetet, látnia kell a Widget testreszabása gombot a három pont menüben. A testreszabás gombra kattintva megjelenik a testreszabási sablon.
A Számláló alaphelyzetbe állítása gombra kattintva állítsa vissza a számlálót 0-ra. Kattintson a Kilépés a testreszabásból gombra a widget szokásos viselkedéséhez való visszatéréshez.
Windows developer