Microsoft just shipped v1.2 of the Windows App SDK, which includes support for creating third-party Widgets for Windows. During the previews, it was understood that there was a limitation that the widget provider (i.e. the functionality of the widget) needs to be developed using C++. However, this changed and the final release included support for writing widgets in C# (with only a few native interop calls). In this post we’ll walk through creating a simple widget in C# and look at how to add it to an existing Windows UI application.
Update 29/12/2022: Here’s the source code for this post
Before we get started, here’s a couple of quick links that are useful for understanding widgets on the Windows platform:
- Widget Provider Overview (documentation)
- Widget Sample (Windows App Sdk Samples)
Note, that there is a C# sample in the works, which you can see an early version of here.
IWidgetProvider
The core element to any widget is the IWidgetProvider interface which is responsible for handling the lifecycle of widgets.
public interface IWidgetProvider
{
void CreateWidget(WidgetContext widgetContext);
void DeleteWidget(string widgetId, string customState);
void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs);
void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs);
void Activate(WidgetContext widgetContext);
void Deactivate(string widgetId);
}
Note that a specific implementation of IWidgetProvider can handle multiple different widget types (we’ll talk about how widget types are registered shortly). There’s isn’t an interface for the widgets themselves, because all interaction with the widgets is brokered through the appropriate IWidgetProvider method. However, as we’ll see, it is useful to define an IWidget interface within your code, to make it easier to separate out the logic for each widget.
IWidget
Before we get into implementing the IWidgetProvider, we’re going to start with creating an IWidget interface. This is going to be used to help us track all the active widget instances that our application is responsible for (ie part of the implementation of IWidgetProvider).
public interface IWidget
{
string? Id { get; }
string? State { get; }
bool IsActivated { get; }
void Activate();
void Deactivate();
void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs);
void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs);
string GetTemplateForWidget();
string GetDataForWidget();
}
As you can see, there are some methods on IWidget that map to the corresponding methods on the IWidgetProvider. For example the OnActionInvoked method on IWidget alignes with the OnActionInvoked method on IWidgetProvider. For these methods, you can probably already imagine that the code for the IWidgetProvider implementation will be something like “find the appropriate Widget Instance, and forward the method call”.
To make it easier to implement our widget logic, we’ll also define a WidgetBase class that will provide some default implementations.
public abstract class WidgetBase : IWidget
{
public string? Id { get; init; }
public virtual string? State { get; set; }
public bool IsActivated { get; private set; }
public virtual void Activate()
{
IsActivated = true;
}
public virtual void Deactivate()
{
IsActivated = false;
}
public virtual void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs) { }
public virtual void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs) { }
public abstract string GetTemplateForWidget();
public abstract string GetDataForWidget();
/// <summary>
/// Retrieves widget template json from file packaged with the application
/// </summary>
/// <param name="packagePath">The path in the package eg "ms-appx:///Templates/WidgetTemplate.json"</param>
/// <returns>Json AdaptiveCards template - use adaptivecards.io to design</returns>
protected string GetTemplateFromFile(string packagePath)
{
var uri = new Uri(packagePath);
// Calling Result is usually not recommended but should be safe to do in the context
// of a widget and this method needs to return synchronously
var storageFile = StorageFile.GetFileFromApplicationUriAsync(uri).AsTask().Result;
return FileIO.ReadTextAsync(storageFile).AsTask().Result;
}
}
BackwardCountingWidget
Our widget for the purpose of this post will simply count down from a starting value of 10000. The implementation code is kept to a minimum by inheriting from WidgetBase.
internal class BackwardCountingWidget : WidgetBase
{
private const int StartingNumber = 10000;
private int _clickCount = StartingNumber;
public override string State {
get => base.State;
set {
base.State = value;
if (string.IsNullOrWhiteSpace(value))
{
State = StartingNumber.ToString();
}
else
{
try
{
// This particular widget stores its clickCount
// in the state as integer. Attempt to parse the saved state
// and convert it to integer.
_clickCount = int.Parse(value);
}
catch
{
// Parsing the state was not successful: cached state might've been corrupted.
State = StartingNumber.ToString();
}
}
}
}
// This function wil be invoked when the Decrement button was clicked by the user.
public override void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var verb = actionInvokedArgs.Verb;
if (verb == "dec")
{
// Decrement the count
_clickCount--;
State = _clickCount + "";
// Generate template/data you want to send back
// The template has not changed so it does not neeed to be updated
var widgetData = GetDataForWidget();
// Build update options and update the widget
WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(Id);
updateOptions.Data = widgetData;
updateOptions.CustomState = State;
WidgetManager.GetDefault().UpdateWidget(updateOptions);
}
}
public override string GetTemplateForWidget()
{
// This widget has the same template for all the sizes/themes so we load it only once.
var widgetTemplate = GetTemplateFromFile("ms-appx:///Templates/BackwardCountingWidgetTemplate.json");
return widgetTemplate;
}
public override string GetDataForWidget()
{
return "{ \"count\": " + State + " }";
}
}
You can see from the GetTemplateForWidget method that there is a reference to the BackwardCountingWidgetTemplate.json – this file contains the json structure that defines the layout of the widget.
{
"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": "Decrement",
"verb": "dec"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
}
WidgetProviderBase
Rather than having to implement the IWidgetProvider logic every time we want to add a widget to an application, we’ve abstracted the majority of the code to a base class, WidgetProviderBase.
public class WidgetProviderBase:IWidgetProvider
{
private record WidgetCreationInfo
(
string WidgetName,
Func<WidgetContext, string, IWidget> Factory
);
public static T CreateWidget<T>(WidgetContext widgetContext, string state)
where T : WidgetBase, new()
{
var widgetId = widgetContext.Id;
var newWidget = new T() { Id = widgetId, State = state };
// Call UpdateWidget() to send data/template to the newly created widget.
WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(widgetId);
updateOptions.Template = newWidget.GetTemplateForWidget();
updateOptions.Data = newWidget.GetDataForWidget();
// You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState = newWidget.State;
// Update the widget
WidgetManager.GetDefault().UpdateWidget(updateOptions);
return newWidget;
}
// Register all widget types here, optionally specify an is enabled function
static IDictionary<string, WidgetCreationInfo> _widgetRegistry = new Dictionary<string, WidgetCreationInfo>();
protected static void RegisterWidget<TWidget>(string widgetName)
where TWidget:WidgetBase, new()
{
_widgetRegistry[widgetName] = new WidgetCreationInfo(widgetName, (ctx, state) => CreateWidget<TWidget>(ctx, state));
}
IDictionary<string, IWidget> _runningWidgets = new Dictionary<string, IWidget>();
protected WidgetProviderBase()
{
RecoverRunningWidgets();
}
public IWidget InitializeWidgetInternal(WidgetContext widgetContext, string state)
{
var widgetName = widgetContext.DefinitionId;
var widgetId = widgetContext.Id;
if (_widgetRegistry.TryGetValue(widgetName, out var creation))
{
var widgetImpl = creation.Factory(widgetContext, state);
_runningWidgets[widgetId] = widgetImpl;
return widgetImpl;
}
return default;
}
public IWidget FindRunningWidget(string widgetId)
{
return _runningWidgets.TryGetValue(widgetId, out var widget) ? widget : default;
}
// Handle the CreateWidget call. During this function call you should store
// the WidgetId value so you can use it to update corresponding widget.
// It is our way of notifying you that the user has pinned your widget
// and you should start pushing updates.
public void CreateWidget(WidgetContext widgetContext)
{
// Since it's a new widget - there's no cached state and we pass an empty string instead.
_ = InitializeWidgetInternal(widgetContext, "");
}
// Handle the DeleteWidget call. This is notifying you that
// you don't need to provide new content for the given WidgetId
// since the user has unpinned the widget or it was deleted by the Host
// for any other reason.
public void DeleteWidget(string widgetId, string customState)
{
_runningWidgets.Remove(widgetId);
}
// Handle the OnActionInvoked call. This function call is fired when the user's
// interaction with the widget resulted in an execution of one of the defined
// actions that you've indicated in the template of the Widget.
// For example: clicking a button or submitting input.
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
if (FindRunningWidget(widgetId) is { } runningWidget)
{
runningWidget.OnActionInvoked(actionInvokedArgs);
}
}
// Handle the WidgetContextChanged call. This function is called when the context a widget
// has changed. Currently it only signals that the user has changed the size of the widget.
// There are 2 ways to respond to this event:
// 1) Call UpdateWidget() with the new data/template to fit the new requested size.
// 2) If previously sent data/template accounts for various sizes based on $host.widgetSize - you can use this event solely for telemtry.
public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs)
{
var widgetContext = contextChangedArgs.WidgetContext;
var widgetId = widgetContext.Id;
if (FindRunningWidget(widgetId) is { } runningWidget)
{
runningWidget.OnWidgetContextChanged(contextChangedArgs);
// Optionally: if the data/template for the new context is different
// from the previously sent data/template - send an update.
}
}
// Handle the Activate call. This function is called when widgets host starts listening
// to the widget updates. It generally happens when the widget becomes visible and the updates
// will be promptly displayed to the user. It's recommended to start sending updates from this moment
// until Deactivate function was called.
public void Activate(Microsoft.Windows.Widgets.Providers.WidgetContext widgetContext)
{
var widgetId = widgetContext.Id;
if (FindRunningWidget(widgetId) is { } runningWidget)
{
runningWidget.Activate();
}
}
// Handle the Deactivate call. This function is called when widgets host stops listening
// to the widget updates. It generally happens when the widget is not visible to the user
// anymore and any further updates won't be displayed until the widget is visible again.
// It's recommended to stop sending updates until Activate function was called.
public void Deactivate(string widgetId)
{
if (FindRunningWidget(widgetId) is { } runningWidget)
{
runningWidget.Deactivate();
}
}
// This function will be called in WidgetProvider Constructor
// to get information about all the widgets that this provider
// is currently providing. It's helpful in case of the Provider
// subsequent launches to restore the previous state of the running widgets.
void RecoverRunningWidgets()
{
try
{
WidgetManager widgetManager = WidgetManager.GetDefault();
foreach (var widgetInfo in widgetManager.GetWidgetInfos())
{
var widgetContext = widgetInfo.WidgetContext;
var widgetId = widgetContext.Id;
var customState = widgetInfo.CustomState;
// Check if this widgetId is known
if (FindRunningWidget(widgetId) is null)
{
// Hook up an instance with this context
// Ignore the return result if this widget is not supported
InitializeWidgetInternal(widgetContext, customState);
}
}
}
catch
{
}
}
}
WidgetProvider
Now we have the WidgetProviderBase, we can implement the minimal additional logic required for our WidgetProvider.
[Guid("B346D60C-42FC-4D9F-AB57-DC263E64F243")]
internal class WidgetProvider:WidgetProviderBase
{
static WidgetProvider()
{
RegisterWidget<BackwardCountingWidget>("BackwardCounting");
}
}
There are two important things to note here. The first being the type of the Widget being registered and it’s name, BackwardCounting. The second, which leads onto the next topic about COM registration is the Guid assigned to the WidgetProvider using the GuidAttribute.
COM Registration
The execution of the widget does not happen in the same process as the main widget pane in Windows, which means that the widget provider needs to be registered as a COM server that can be invoked as required. We have a simple wrapper class, COMServer, which can be used to register and invoke the IWidgetProvider.
public sealed class ComServer<TInterface, TImplementation>
where TImplementation:TInterface, new()
{
private ComServer() { }
private static ComServer<TInterface, TImplementation> _instance = new ComServer<TInterface, TImplementation>();
public static ComServer<TInterface, TImplementation> Instance
{
get { return _instance; }
}
// For thread-sync in lock
private object syncRoot = new object();
// Whether the server is running
private bool _bRunning = false;
/// <summary>
/// Run the COM server. If the server is running, the function
/// returns directly.
/// </summary>
/// <remarks>The method is thread-safe.</remarks>
public void Run()
{
lock (syncRoot) // Ensure thread-safe
{
// If the server is running, return directly.
if (_bRunning)
return;
// Indicate that the server is running now.
_bRunning = true;
}
try
{
//
// Register the COM class factories.
//
var widget_provider_clsid = HelperMethods.GetGuidFromType(typeof(TImplementation));
// Register the SimpleObject class object
int hResult = NativeMethods.CoRegisterClassObject(
ref widget_provider_clsid,
new TypedClassFactory<TInterface, TImplementation>(),
NativeMethods.CLSCTX.LOCAL_SERVER,
NativeMethods.REGCLS.MULTIPLEUSE,
out var widgetProviderFactory);
if (hResult != 0)
{
throw new ApplicationException(
"CoRegisterClassObject failed w/err 0x" + hResult.ToString("X"));
}
IntPtr evt = NativeMethods.CreateEvent(IntPtr.Zero, true, false, IntPtr.Zero);
hResult = NativeMethods.CoWaitForMultipleObjects(
CWMO_FLAGS.CWMO_DISPATCH_CALLS | CWMO_FLAGS.CWMO_DISPATCH_WINDOW_MESSAGES,
0xFFFFFFFF, 1, new IntPtr[] { evt }, out uint index
);
if (hResult != 0)
{
// Revoke the registration of SimpleObject on failure
if (widgetProviderFactory != 0)
{
NativeMethods.CoRevokeClassObject(widgetProviderFactory);
}
}
}
finally
{
lock (syncRoot) // Ensure thread-safe
{
_bRunning = false;
}
}
}
}
Program
By default, your Windows App SDK / Windows UI application will automatically generate the Program class with Main entrypoint method. In order to invoke the IWidgetProvider, we need to intercept the startup logic and look for the RegisterProcessAsComServer argument. To do this we first need to disable the generated Main method by adding the following constant definition into the csproj file of our application.
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
Next, we need to add our own Program class with the following implementation.
public class Program
{
[STAThread]
static async Task Main(string[] args)
{
WinRT.ComWrappersSupport.InitializeComWrappers();
if (args?.Any(x => x.Contains("RegisterProcessAsComServer")) ?? false)
{
ComServer<IWidgetProvider, WidgetProvider>.Instance.Run();
}
else
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
}
}
Manifest File
The last thing that’s required, in order to glue this all together is an update to the package.appxmanifest file. So far, whilst we’ve created our IWidgetProvider implementation and we’ve update the Program class to handle the RegisterProcessAsComServer request, we have told Windows that our IWidgetProvider exist, nor what type of Widgets it supports. To do this, we need to add two elements to the manifest file.
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap uap3 rescap">
<Identity
Name="53480a44-1691-4ed8-989e-864eb356cf1b"
Publisher="CN=NickRandolph"
Version="1.0.0.0" />
<Properties>
<DisplayName>MyManagedWidgetDemo</DisplayName>
<PublisherDisplayName>NickRandolph</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="MyManagedWidgetDemo"
Description="MyManagedWidgetDemo"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="MyManagedWidgetDemo.exe" Arguments="-RegisterProcessAsComServer" DisplayName="MyManagedWidgetDemo">
<com:Class Id="B346D60C-42FC-4D9F-AB57-DC263E64F243" DisplayName="MyWidgetProvider" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="MyManagedWidget" Id="MyManagedWidget" PublicFolder="Public">
<uap3:Properties>
<WidgetProvider>
<ProviderIcons>
<Icon Path="Images\StoreLogo.png" />
</ProviderIcons>
<Activation>
<CreateInstance ClassId="B346D60C-42FC-4D9F-AB57-DC263E64F243" />
</Activation>
<Definitions>
<Definition Id="BackwardCounting"
DisplayName="Sample Backward Counting Widget"
Description="Backward Widget Description">
<Capabilities>
<Capability>
<Size Name="small" />
</Capability>
<Capability>
<Size Name="medium" />
</Capability>
<Capability>
<Size Name="large" />
</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 />
<LightMode />
</ThemeResources>
</Definition>
</Definitions>
</WidgetProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
The two elements we’ve added are under the Extensions element, com:Extension Category="windows.comServer"
and uap3:Extension Category="windows.appExtension"
. Essentially the first defines the COM server (ie the IWidgetProvider), whilst the second defines the type of widget (the Id of the widget definition is BackwardCounting, which matches the name we registered in the WidgetProvider implementation).
Debugging the Widget
It’s quite easy to debug the widget code by setting breakpoints and stepping through the logic as you would be used to with regular application logic. The only difference is that unlike an app, where you can just run from within Visual Studio, with a widget, you have to create the widget via the widgets pane in Windows. In order for breakpoints to be hit, Visual Studio needs to know to attach to the process, when it’s created. There are a couple of ways to do this but the easiest is to go into the properties for your application (right-click the application in Solution Explorer and select Properties), select Debug/General and then click the Open debug launch profiles UI.
Check the “Do not launch app, but debug my code when it starts” option. Close the dialog to save the property changes.
Now you can press F5 or click the Run button in Visual Studio. The application won’t launch until you either start the application from the Start menu or you add a widget to the widgets pane.
Open the Widgets pane and click the + button in the top right corner of the pane.
Click the + button alongside your widget. This will add your widget to the widgets pane and invoke the Main method on the Program class with the RegisterProcessAsComServer argument.
If you have breakpoints set you should see that the breakpoints are set.
And that’s it – that’s how you can create and debug an widget to go alongside your Windows App Sdk application.
Additional Code
The COMServer class is dependent on a couple of other classes, which are included here for completeness:
IClassFactory
/// <summary>
/// You must implement this interface for every class that you register in
/// the system registry and to which you assign a CLSID, so objects of that
/// class can be created.
/// http://msdn.microsoft.com/en-us/library/ms694364.aspx
/// </summary>
[ComImport]
[ComVisible(false)]
[Guid(NativeMethods.IID_IClassFactory)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IClassFactory
{
/// <summary>
/// Creates an uninitialized object.
/// </summary>
/// <param name="outer"></param>
/// <param name="iid">
/// Reference to the identifier of the interface to be used to
/// communicate with the newly created object. If pUnkOuter is NULL, this
/// parameter is frequently the IID of the initializing interface.
/// </param>
/// <param name="result">
/// Address of pointer variable that receives the interface pointer
/// requested in riid.
/// </param>
/// <returns>S_OK means success.</returns>
[PreserveSig]
int CreateInstance(IntPtr outer, ref Guid iid, out IntPtr result);
/// <summary>
/// Locks object application open in memory.
/// </summary>
/// <param name="fLock">
/// If TRUE, increments the lock count;
/// if FALSE, decrements the lock count.
/// </param>
/// <returns>S_OK means success.</returns>
[PreserveSig]
int LockServer(bool fLock);
}
HelperMethods
internal static class HelperMethods
{
public static Guid GetGuidFromType(Type t)
{
if (t == null)
throw new ArgumentNullException("Type object cannot be null.", "t");
object[] attributes = t.GetCustomAttributes(typeof(GuidAttribute), false);
if (attributes.Length < 1)
throw new ArgumentException("No GUID attribute specified on the type " + t.Name);
GuidAttribute attr = (GuidAttribute)attributes[0];
return new Guid(attr.Value);
}
}
TypedClassFactory
internal class TypedClassFactory<TInterface, TImplementation>
: IClassFactory
where TImplementation : TInterface, new()
{
public int CreateInstance(IntPtr outer, ref Guid iid,
out IntPtr result)
{
if (outer != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(NativeMethods.CLASS_E_NOAGGREGATION);
}
result = MarshalInspectable<TInterface>.FromManaged(new TImplementation());
return 0; // S_OK
}
public int LockServer(bool fLock)
{
return 0; // S_OK
}
}
NativeMethods
internal static class NativeMethods
{
/// <summary>
/// Get current thread ID.
/// </summary>
/// <returns></returns>
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int GetCurrentThreadId();
/// <summary>
/// Get current process ID.
/// </summary>
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int GetCurrentProcessId();
[DllImport("ole32.dll")]
public static extern int CoWaitForMultipleObjects(
CWMO_FLAGS dwFlags, uint dwTimeout,
int cHandles, IntPtr[] pHandles, out uint lpdwindex);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateEvent(IntPtr eventAttributes,
[MarshalAs(UnmanagedType.Bool)] bool manualReset,
[MarshalAs(UnmanagedType.Bool)] bool initialState,
IntPtr name);
/// <summary>
/// The GetMessage function retrieves a message from the calling thread's
/// message queue. The function dispatches incoming sent messages until a
/// posted message is available for retrieval.
/// </summary>
/// <param name="lpMsg">
/// Pointer to an MSG structure that receives message information from
/// the thread's message queue.
/// </param>
/// <param name="hWnd">
/// Handle to the window whose messages are to be retrieved.
/// </param>
/// <param name="wMsgFilterMin">
/// Specifies the integer value of the lowest message value to be
/// retrieved.
/// </param>
/// <param name="wMsgFilterMax">
/// Specifies the integer value of the highest message value to be
/// retrieved.
/// </param>
/// <returns></returns>
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMessage(
out NativeMessage lpMsg,
IntPtr hWnd,
[MarshalAs(UnmanagedType.U4)] int wMsgFilterMin,
[MarshalAs(UnmanagedType.U4)] int wMsgFilterMax);
/// <summary>
/// The TranslateMessage function translates virtual-key messages into
/// character messages. The character messages are posted to the calling
/// thread's message queue, to be read the next time the thread calls the
/// GetMessage or PeekMessage function.
/// </summary>
/// <param name="lpMsg"></param>
/// <returns></returns>
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage(
[In] ref NativeMessage lpMsg);
/// <summary>
/// The DispatchMessage function dispatches a message to a window
/// procedure. It is typically used to dispatch a message retrieved by
/// the GetMessage function.
/// </summary>
/// <param name="lpMsg"></param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr DispatchMessage(
[In] ref NativeMessage lpMsg);
/// <summary>
/// The PostThreadMessage function posts a message to the message queue
/// of the specified thread. It returns without waiting for the thread to
/// process the message.
/// </summary>
/// <param name="idThread">
/// Identifier of the thread to which the message is to be posted.
/// </param>
/// <param name="Msg">Specifies the type of message to be posted.</param>
/// <param name="wParam">
/// Specifies additional message-specific information.
/// </param>
/// <param name="lParam">
/// Specifies additional message-specific information.
/// </param>
/// <returns></returns>
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PostThreadMessage(
[MarshalAs(UnmanagedType.U4)] int idThread,
[MarshalAs(UnmanagedType.U4)] int Msg,
IntPtr wParam,
IntPtr lParam);
/// <summary>
/// CoInitializeEx() can be used to set the apartment model of individual
/// threads.
/// </summary>
/// <param name="pvReserved">Must be NULL</param>
/// <param name="dwCoInit">
/// The concurrency model and initialization options for the thread
/// </param>
/// <returns></returns>
[DllImport("ole32.dll")]
public static extern int CoInitializeEx(
IntPtr pvReserved,
[MarshalAs(UnmanagedType.U4)] int dwCoInit);
/// <summary>
/// CoUninitialize() is used to uninitialize a COM thread.
/// </summary>
[DllImport("ole32.dll")]
public static extern void CoUninitialize();
/// <summary>
/// Registers an EXE class object with OLE so other applications can
/// connect to it. EXE object applications should call
/// CoRegisterClassObject on startup. It can also be used to register
/// internal objects for use by the same EXE or other code (such as DLLs)
/// that the EXE uses.
/// </summary>
/// <param name="rclsid">CLSID to be registered</param>
/// <param name="pUnk">
/// Pointer to the IUnknown interface on the class object whose
/// availability is being published.
/// </param>
/// <param name="dwClsContext">
/// Context in which the executable code is to be run.
/// </param>
/// <param name="flags">
/// How connections are made to the class object.
/// </param>
/// <param name="lpdwRegister">
/// Pointer to a value that identifies the class object registered;
/// </param>
/// <returns></returns>
/// <remarks>
/// PInvoking CoRegisterClassObject to register COM objects is not
/// supported.
/// </remarks>
[DllImport("ole32.dll")]
public static extern int CoRegisterClassObject(
ref Guid rclsid,
[MarshalAs(UnmanagedType.Interface)] IClassFactory pUnk,
[MarshalAs(UnmanagedType.U4)] CLSCTX dwClsContext,
[MarshalAs(UnmanagedType.U4)] REGCLS flags,
[Out, MarshalAs(UnmanagedType.U4)] out int lpdwRegister);
/// <summary>
/// Informs OLE that a class object, previously registered with the
/// CoRegisterClassObject function, is no longer available for use.
/// </summary>
/// <param name="dwRegister">
/// Token previously returned from the CoRegisterClassObject function
/// </param>
/// <returns></returns>
[DllImport("ole32.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int CoRevokeClassObject(
[MarshalAs(UnmanagedType.U4)] int dwRegister);
/// <summary>
/// Called by a server that can register multiple class objects to inform
/// the SCM about all registered classes, and permits activation requests
/// for those class objects.
/// </summary>
/// <returns></returns>
/// <remarks>
/// Servers that can register multiple class objects call
/// CoResumeClassObjects once, after having first called
/// CoRegisterClassObject, specifying REGCLS_LOCAL_SERVER |
/// REGCLS_SUSPENDED for each CLSID the server supports. This function
/// causes OLE to inform the SCM about all the registered classes, and
/// begins letting activation requests into the server process.
///
/// This reduces the overall registration time, and thus the server
/// application startup time, by making a single call to the SCM, no
/// matter how many CLSIDs are registered for the server. Another
/// advantage is that if the server has multiple apartments with
/// different CLSIDs registered in different apartments, or is a free-
/// threaded server, no activation requests will come in until the server
/// calls CoResumeClassObjects. This gives the server a chance to
/// register all of its CLSIDs and get properly set up before having to
/// deal with activation requests, and possibly shutdown requests.
/// </remarks>
[DllImport("ole32.dll")]
public static extern int CoResumeClassObjects();
public const int WM_QUIT = 0x0012;
/// <summary>
/// Interface Id of IClassFactory
/// </summary>
public const string IID_IClassFactory = "00000001-0000-0000-C000-000000000046";
/// <summary>
/// Interface Id of IUnknown
/// </summary>
public const string IID_IUnknown = "00000000-0000-0000-C000-000000000046";
/// <summary>
/// Interface Id of IDispatch
/// </summary>
public const string IID_IDispatch = "00020400-0000-0000-C000-000000000046";
/// <summary>
/// Class does not support aggregation (or class object is remote)
/// </summary>
public const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
/// <summary>
/// No such interface supported
/// </summary>
public const int E_NOINTERFACE = unchecked((int)0x80004002);
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr hWnd;
[MarshalAs(UnmanagedType.U4)]
public int message;
public IntPtr wParam;
public IntPtr lParam;
[MarshalAs(UnmanagedType.U4)]
public int time;
public NativePoint pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct NativePoint
{
public int X;
public int Y;
}
/// <summary>
/// Values from the CLSCTX enumeration are used in activation calls to
/// indicate the execution contexts in which an object is to be run. These
/// values are also used in calls to CoRegisterClassObject to indicate the
/// set of execution contexts in which a class object is to be made available
/// for requests to construct instances.
/// </summary>
[Flags]
public enum CLSCTX : int
{
INPROC_SERVER = 0x1,
INPROC_HANDLER = 0x2,
LOCAL_SERVER = 0x4,
INPROC_SERVER16 = 0x8,
REMOTE_SERVER = 0x10,
INPROC_HANDLER16 = 0x20,
RESERVED1 = 0x40,
RESERVED2 = 0x80,
RESERVED3 = 0x100,
RESERVED4 = 0x200,
NO_CODE_DOWNLOAD = 0x400,
RESERVED5 = 0x800,
NO_CUSTOM_MARSHAL = 0x1000,
ENABLE_CODE_DOWNLOAD = 0x2000,
NO_FAILURE_LOG = 0x4000,
DISABLE_AAA = 0x8000,
ENABLE_AAA = 0x10000,
FROM_DEFAULT_CONTEXT = 0x20000,
ACTIVATE_32_BIT_SERVER = 0x40000,
ACTIVATE_64_BIT_SERVER = 0x80000
}
[Flags]
public enum CWMO_FLAGS : int
{
CWMO_DEFAULT = 0,
CWMO_DISPATCH_CALLS = 1,
CWMO_DISPATCH_WINDOW_MESSAGES = 2
};
/// <summary>
/// The REGCLS enumeration defines values used in CoRegisterClassObject to
/// control the type of connections to a class object.
/// </summary>
[Flags]
public enum REGCLS : int
{
SINGLEUSE = 0,
MULTIPLEUSE = 1,
MULTI_SEPARATE = 2,
SUSPENDED = 4,
SURROGATE = 8,
}
}
4 thoughts on “Adding a Windows Widget to a C# Windows App SDK (Windows UI) App”