Creating chromeless, non-rectangular Windows from UWP

Picture3

On my way back home from the IoT in Action event in Taipei I had a several hours time to work on a small personal spare-time app project that I am hoping to publish to the Store soon. One of my requirements involves a creating a secondary chromeless, non-rectangular utility window (that is also semi-transparent and topmost). UWP’s ApplicationView/Window doesn’t support chromeless, non-rectangular windows – but with the Windows application model being an à la carte platform, this can be accomplished by adding a desktop extension to my UWP app package that uses a WPF Window to provide the required rich windowing capabilities. My app/main window itself needs be UWP because of some other requirements.

I figured it would be useful to put together a quick sample and write-up for this, as I don’t believe we’ve ever had any docs/blogs/sample to date that describe how to create an arbitrarily shaped chromeless window from a UWP app. How arbitrary the shape can be you ask? Well, if you can draw it, it can be your window’s shape! To prove my claim my sample actually lets you hand-draw windows using the UWP InkCanvas control – check it out in the video:

In the secondary WPF windows you would typically host WPF controls to implement your UI – and now with XAML Islands (introduced in 1809) you could also host UWP controls. Note though that the XAML Island itself needs to be rectangular, if you decide to use one. In my sample app here I am just hosting a re-styled WPF button that serves as my close button. The sample app works on build 1709 and above (though the features used here are already available since build 1607). Read on for details on how to implement this scenario with just a couple of lines of code.

Tl;Dr;

Just show me the code on GitHub and let me install the sample app from the Store …

Sample Source Code
https://github.com/StefanWickDev/ExtensionGallery/tree/master/Sketch-a-Window

Sample App Installer:
https://www.microsoft.com/store/productId/9NNMBR5MH63W

 

Source Code Walkthrough

Project Structure

The Visual Studio solution for this sample consists of three projects:

  1. The actual UWP main app (“Sketch-a-Window”)
  2. A WPF project that provides the windowing feature (“WPF”)
  3. A Windows App Packaging project to package the two together for distribution (“Package”)

projects

Package Manifest

The appxmanifest file in the Package project is the one that is relevant at deployment time. Here we need to add the declaration for our desktop extension, so that the UWP app can activate the WPF process for creating the windows.

<Extensions>
  <desktop:Extension Category="windows.fullTrustProcess" Executable="WPF\WPF.exe" />
</Extensions>

UWP Code

The gist on the UWP side is that we are using the FullTrustProcessLauncher API to create the WPF window. To pass on the geometry details about the shape of the window we are using the local AppData settings. Most of the code here is specific to getting the geometry info from the collected ink stroke and passing it down to the WPF. You obviously won’t need all this code if you are creating windows of well-known shapes.

private async void InkPresenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
    Rect viewBounds = ApplicationView.GetForCurrentView().VisibleBounds;
    Rect inkBounds = args.Strokes[0].BoundingRect;
    Rect winBounds = Window.Current.Bounds;
    ApplicationData.Current.LocalSettings.Values["X"] = viewBounds.X + inkBounds.Left;
    ApplicationData.Current.LocalSettings.Values["Y"] = viewBounds.Y + inkBounds.Top;
    ApplicationData.Current.LocalSettings.Values["Width"] = winBounds.Width;
    ApplicationData.Current.LocalSettings.Values["Height"] = winBounds.Height;
            
    var inkPoints = args.Strokes[0].GetInkPoints();
    var rawPoints = new double[inkPoints.Count * 2];
    for(int i=0; i<inkPoints.Count; i++)
    {
        rawPoints[2 * i]     = inkPoints[i].Position.X - inkBounds.Left;
        rawPoints[2 * i + 1] = inkPoints[i].Position.Y - inkBounds.Top;
    }
    SavePointsToSettings(rawPoints);            

    if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
    {
        await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }
    await Task.Delay(1000);
    sender.StrokeContainer.Clear();
}

WPF Code

Now on the WPF side, we are creating a window with the desired rich capabilities, specifically in this case we want to allow transparency, remove the chrome and make it topmost, which can be declared in markup like this:

<Window x:Class="WPF.MainWindow"
        ...
        WindowStyle="None" Topmost="True" AllowsTransparency="True">

In the code-behind we need to unpack the geometry information about the ink stroke and clip the window accordingly. Again, most of the code here is specific to the sample dealing with complex shapes defined by ink strokes and you won’t need it when creating windows with well-known shapes:

public MainWindow()
{
    this.Left = (double)ApplicationData.Current.LocalSettings.Values["X"];
    this.Top = (double)ApplicationData.Current.LocalSettings.Values["Y"];
    this.Width = (double)ApplicationData.Current.LocalSettings.Values["Width"];
    this.Height = (double)ApplicationData.Current.LocalSettings.Values["Height"];

    InitializeComponent();
    this.MouseMove += MainWindow_MouseMove;

    RadialGradientBrush brush = new RadialGradientBrush();
    brush.GradientStops.Add(new GradientStop(GetRandomColor(), 0.0d));
    brush.GradientStops.Add(new GradientStop(GetRandomColor(), 0.5d));
    brush.GradientStops.Add(new GradientStop(GetRandomColor(), 1.0d));
    root.Background = brush;

    double[] rawPoints = (double[])ApplicationData.Current.LocalSettings.Values["Points"];
    PathGeometry geo = new PathGeometry();
    geo.FillRule = FillRule.Nonzero;
    PathFigure figure = new PathFigure();
    figure.StartPoint = new Point(rawPoints[0], rawPoints[1]);
    PolyLineSegment segment = new PolyLineSegment();
    for (int i = 0; i < rawPoints.Length / 2; i+=2)
    {
        segment.Points.Add(new Point(rawPoints[2 * i], rawPoints[2 * i +1]));
    }
    figure.Segments.Add(segment);
    geo.Figures.Add(figure);
    this.Clip = geo;

    closeButton.SetValue(Canvas.LeftProperty, geo.Bounds.Width / 2 - geo.Bounds.Left - 16);
    closeButton.SetValue(Canvas.TopProperty, geo.Bounds.Height / 2 - geo.Bounds.Top - 16);
}

 

Conclusion

If you ever need to add a utility window to your UWP app that is chromeless and/or has an arbitrary shape – or using any of the other rich windowing capabilities that are still missing in UWP, using a desktop extension with a WPF window is a way to accomplish the scenario.

This was a fun sample to put together. I will now continue working on my actual personal app project that triggered the idea for this post. Will share the actual use case in an upcoming post. Follow me on twitter @StefanWickDev to get notfied. Hope you found this information helpful and maybe you can use it in one of your projects as well.

Picture3

Advertisements