The documentation for printing in JavaScript UWP apps is out of date as it all references MSApp.getHtmlPrintDocumentSource but that method has been replaced by MSApp.getHtmlPrintDocumentSourceAsync since WinPhone 8.1.
Previous to WinPhone 8.1 the WebView's HTML content ran on the UI thread of the app. This is troublesome for rendering arbitrary web content since in the extreme case the JavaScript of some arbitrary web page might just sit in a loop and never return control to your app's UI. With WinPhone 8.1 we added off thread WebView in which the WebView runs HTML content on a separate UI thread.
Off thread WebView required changing our MSApp.getHtmlPrintDocumentSource API which could no longer synchronously produce an HtmlPrintDocumentSource. With WebViews running on their own threads it may take some time for them to generate their print content for the HtmlPrintDocumentSource and we don't want to hang the app's UI thread in the interim. So the MSApp.getHtmlPrintDocumentSource API was replaced with MSApp.getHtmlPrintDocumentSourceAsync which returns a promise the resolved value of which is the eventual HtmlPrintDocumentSource.
However, the usage of the API is otherwise unchanged. So in sample code you see referencing MSApp.getHtmlPrintDocumentSource the sample code is still reasonable but you need to call MSApp.getHtmlPrintDocumentSourceAsync instead and wait for the promise to complete. For example the PrintManager docs has an example implementing a PrintTaskRequested event handler in a JavaScript UWP app.
function onPrintTaskRequested(printEvent) {
var printTask = printEvent.request.createPrintTask("Print Sample", function (args) {
args.setSource(MSApp.getHtmlPrintDocumentSource(document));
});
Instead we need to obtain a deferral in the event handler so we can asynchronously wait for getHtmlPrintDocumentSourceAsync to complete:
function onPrintTaskRequested(printEvent) {
var printTask = printEvent.request.createPrintTask("Print Sample", function (args) {
const deferral = args.getDeferral();
MSApp.getHtmlPrintDocumentSourceAsync(document).then(htmlPrintDocumentSource => {
args.setSource(htmlPrintDocumentSource);
deferral.complete();
}, error => {
console.error("Error: " + error.message + " " + error.stack);
deferral.complete();
});
});
Parsing WinMD files, the containers of WinRT API metadata, is relatively simple using the appropriate .NET reflection APIs. However, figuring out which reflection APIs to use is not obvious. I've got a completed C sharp class parsing WinMD files that you can check out for reference.
Use System.Reflection.Assembly.ReflectionOnlyLoad
to load the
WinMD file. Don't use the normal load methods because the WinMD files contain only metadata. This will load up info about APIs defined in that WinMD, but any references to types outside of that
WinMD including types found in the normal OS system WinMD files must be resolved by the app code via the System.Reflection.InteropServices.WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve
event.
In this event handler you must resolve the unknown namespace reference by adding an assembly to the NamespaceResolveEventArgs's ResolvedAssemblies property. If you're only interested in OS system
WinMD files you can use System.Reflection.InteropServices.WindowsRuntimeMetadata.ResolveNamespace
to
turn a namespace into the expected OS system WinMD path and turn that path into an assembly with ReflectionOnlyLoad.
I've made a PowerShell script to show system toast notifications with WinRT and PowerShell. Along the way I learned several interesting things.
First off calling WinRT from PowerShell involves a strange syntax. If you want to use a class you write [-Class-,-Namespace-,ContentType=WindowsRuntime] first to tell PowerShell about the type. For example here I create a ToastNotification object:
[void][Windows.UI.Notifications.ToastNotification,Windows.UI.Notifications,ContentType=WindowsRuntime];
$toast = New-Object Windows.UI.Notifications.ToastNotification -ArgumentList $xml;
And
here I call the static method CreateToastNotifier on the ToastNotificationManager class:
[void][Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications,ContentType=WindowsRuntime];
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppUserModelId);
With
this I can call WinRT methods and this is enough to show a toast but to handle the click requires a little more work.
To handle the user clicking on the toast I need to listen to the Activated event on the Toast object. However Register-ObjectEvent doesn't handle WinRT events. To work around this I created a .NET event wrapper class to turn the WinRT event into a .NET event that Register-ObjectEvent can handle. This is based on Keith Hill's blog post on calling WinRT async methods in PowerShell. With the event wrapper class I can run the following to subscribe to the event:
function WrapToastEvent {
param($target, $eventName);
Add-Type -Path (Join-Path $myPath "PoshWinRT.dll")
$wrapper = new-object "PoshWinRT.EventWrapper[Windows.UI.Notifications.ToastNotification,System.Object]";
$wrapper.Register($target, $eventName);
}
[void](Register-ObjectEvent -InputObject (WrapToastEvent $toast "Activated") -EventName FireEvent -Action {
...
});
To handle the Activated event I want to put focus back on the PowerShell window that created the toast. To do this I need to call the Win32 function SetForegroundWindow. Doing so from PowerShell is surprisingly easy. First you must tell PowerShell about the function:
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class PInvoke {
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hwnd);
}
"@
Then
to call:
[PInvoke]::SetForegroundWindow((Get-Process -id $myWindowPid).MainWindowHandle);
But figuring out the HWND to give to SetForegroundWindow isn't totally straight forward. Get-Process exposes a MainWindowHandle property but if you start a cmd.exe prompt and then run PowerShell inside of that, the PowerShell process has 0 for its MainWindowHandle property. We must follow up process parents until we find one with a MainWindowHandle:
$myWindowPid = $pid;
while ($myWindowPid -gt 0 -and (Get-Process -id $myWindowPid).MainWindowHandle -eq 0) {
$myWindowPid = (gwmi Win32_Process -filter "processid = $($myWindowPid)" | select ParentProcessId).ParentProcessId;
}
MSDN covers the topic of JavaScript and WinRT type conversions provided by Chakra (JavaScript Representation of Windows Runtime Types and Considerations when Using the Windows Runtime API), but for the questions I get about it I’ll try to lay out some specifics of that discussion more plainly. I’ve made a TL;DR JavaScript types and WinRT types summary table.
WinRT | Conversion | JavaScript |
---|---|---|
Struct | ↔️ | JavaScript object with matching property names |
Class or interface instance | ➡ | JavaScript object with matching property names |
Windows.Foundation.Collections.IPropertySet | ➡ | JavaScript object with arbitrary property names |
Any | ⃠ | DOM object |
Chakra, the JavaScript engine powering the Edge browser and JavaScript Windows Store apps, does the work to project WinRT into JavaScript. It is responsible for, among other things, converting back and forth between JavaScript types and WinRT types. Some basics are intuitive, like a JavaScript string is converted back and forth with WinRT’s string representation. For other basic types check out the MSDN links at the top of the page. For structs, interface instances, class instances, and objects things are more complicated.
A struct, class instance, or interface instance in WinRT is projected into JavaScript as a JavaScript object with corresponding property names and values. This JavaScript object representation of a WinRT type can be passed into other WinRT APIs that take the same underlying type as a parameter. This JavaScript object is special in that Chakra keeps a reference to the underlying WinRT object and so it can be reused with other WinRT APIs.
However, if you start with plain JavaScript objects and want to interact with WinRT APIs that take non-basic WinRT types, your options are less plentiful. You can use a plain JavaScript object as a WinRT struct, so long as the property names on the JavaScript object match the WinRT struct’s. Chakra will implicitly create an instance of the WinRT struct for you when you call a WinRT method that takes that WinRT struct as a parameter and fill in the WinRT struct’s values with the values from the corresponding properties on your JavaScript object.
// C# WinRT component
public struct ExampleStruct
{
public string String;
public int Int;
}
public sealed class ExampleStructContainer
{
ExampleStruct value;
public void Set(ExampleStruct value)
{
this.value = value;
}
public ExampleStruct Get()
{
return this.value;
}
}
// JS code
var structContainer = new ExampleWinRTComponent.ExampleNamespace.ExampleStructContainer();
structContainer.set({ string: "abc", int: 123 });
console.log("structContainer.get(): " + JSON.stringify(structContainer.get()));
// structContainer.get(): {"string":"abc","int":123}
You cannot have a plain JavaScript object and use it as a WinRT class instance or WinRT interface instance. Chakra does not provide such a conversion even with ES6 classes.
You cannot take a JavaScript object with arbitrary property names that are unknown at compile time and don’t correspond to a specific WinRT struct and pass that into a WinRT method. If you need to do this, you have to write additional JavaScript code to explicitly convert your arbitrary JavaScript object into an array of property name and value pairs or something else that could be represented in WinRT.
However, the other direction you can do. An instance of a Windows.Foundation.Collections.IPropertySet implementation in WinRT is projected into JavaScript as a JavaScript object with property names and values corresponding to the key and value pairs in the IPropertySet. In this way you can project a WinRT object as a JavaScript object with arbitrary property names and types. But again, the reverse is not possible. Chakra will not convert an arbitrary JavaScript object into an IPropertySet.
// C# WinRT component
public sealed class PropertySetContainer
{
private Windows.Foundation.Collections.IPropertySet otherValue = null;
public Windows.Foundation.Collections.IPropertySet other
{
get
{
return otherValue;
}
set
{
otherValue = value;
}
}
}
public sealed class PropertySet : Windows.Foundation.Collections.IPropertySet
{
private IDictionary map = new Dictionary();
public PropertySet()
{
map.Add("abc", "def");
map.Add("ghi", "jkl");
map.Add("mno", "pqr");
}
// ... rest of PropertySet implementation is simple wrapper around the map member.
// JS code
var propertySet = new ExampleWinRTComponent.ExampleNamespace.PropertySet();
console.log("propertySet: " + JSON.stringify(propertySet));
// propertySet: {"abc":"def","ghi":"jkl","mno":"pqr"}
var propertySetContainer = new ExampleWinRTComponent.ExampleNamespace.PropertySetContainer();
propertySetContainer.other = propertySet;
console.log("propertySetContainer.other: " + JSON.stringify(propertySetContainer.other));
// propertySetContainer.other: {"abc":"def","ghi":"jkl","mno":"pqr"}
try {
propertySetContainer.other = { "123": "456", "789": "012" };
}
catch (e) {
console.error("Error setting propertySetContainer.other: " + e);
// Error setting propertySetContainer.other: TypeError: Type mismatch
}
There’s also no way to implicitly convert a DOM object into a WinRT type. If you want to write third party WinRT code that interacts with the DOM, you must do so indirectly and explicitly in JavaScript code that is interacting with your third party WinRT. You’ll have to extract the information you want from your DOM objects to pass into WinRT methods and similarly have to pass messages out from WinRT that say what actions the JavaScript should perform on the DOM.