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

22 thoughts on “Creating chromeless, non-rectangular Windows from UWP

  1. Hi Stefan,
    I have downloaded this code from git.
    I have followed all the steps as mentioned here.
    When i am trying to build the solution (Where–>Package as startup project).
    I am getting below error.

    The OutputPath property is not set for project ‘Sketch-a-Window.csproj’.
    Please check to make sure that you have specified a valid combination of Configuration and Platform for this project. Configuration=’Debug’ Platform=’AnyCPU’.
    This error may also appear if some other project is trying to follow a project-to-project reference to this project, this project has been unloaded or is not included in the solution, and the referencing project does not build using the same or an equivalent Configuration or Platform.

    Like

      1. I have set for debug and release mode both.
        But my WPF App is in AnyCPU Which might be causing the issue.
        Kindly help in this.

        Like

      2. I just tried it out and I can’t reproduce your problem. Here are the steps I am using:
        1. clone the GitHub repo
        2. open the *.sln VS solution
        3. set “Package” as the start up project
        4. change the architecture to ‘x64’ using the drop down menu in VS
        5. Hit F5 and the app runs without error

        Like

      1. Hi Stefan
        Thanks for the quick reply.
        My version is,
        Microsoft Visual Studio Community Edition version 16.0.3.
        Let me update this and check if works for me

        Like

  2. Hi Stefan
    I have a use case where i am getting stream data from a Bluetooth device for which i am using UWP(as device not supported to WPF).
    Now i want to use pass this stream data to WPF which should be live as well.
    I have tried with UWP Class Lib and tried to add a ref in WPF but not a piece of luck (the error is we can not add UWP class lib project to WPF)
    Kindly suggest any way to do this.

    Like

    1. What do you mean by “device not supported to WPF”? A WPF app should be able to call those Bluetooth API. With few exceptions, WPF apps should be able to call modern Windows APIs as well – have you tried it?

      Consuming a class library won’t work, but if you make your UWP component a proper Windows Runtime component you should be able to consume it in WPF. But as I mentioned above that extra step shouldn’t be needed as you can likely just call the API directly from you WPF app.

      At this point we have gone fairly off-topic here and I would suggest you take further questions over to stackoverflow. You’ll need to include more details though as to what APIs exactly you depend on in order to make your question actionable.

      Liked by 1 person

      1. Hi Stefan,

        I am trying to expose BluetoothLEDevice GattServies’s API of UWP in WPF.
        Error is: The open WPF window crashed OR Not responding with no error at build time.

        I have followed the below blog to expose a simple notification of UWP method to WPF but not works.
        https://blogs.msdn.microsoft.com/appconsult/2018/08/02/using-a-windows-runtime-component-from-a-win32-application/

        I am able to make transparent UI side in WOF as per your guidelines (thanks for your fantastic work and help).I am stuck to pass Stream data from UWP to WPF.

        Is there any way that we can send stream data(get from BLE Device) from UWP to WPF?

        Like

      2. I would expect this API to work also from a WPF app. If it crashes this sounds like a bug that should get fixed. It’s outside my area of expertise and I no longer work in Windows, so I am not an authority to help here. I would suggest you open a question on StackOverflow about calling those Bluetooth APIs from WPF, to see if anyone has done it successfully, and if not the question will get surfaced to Microsoft support folks to follow up further.

        Liked by 1 person

  3. Hi Stefan,
    Do you have any idea when UWP may support Chromeless and transparent window (as we are able to do in wpf –>By Setting Window Style–> None,Allow Transparent –> true,Background->transparent).

    Or Kindly suggest me a link where i can get what is the Microsoft priority for this.

    Like

  4. Hi Stefan,

    I have made a little change in this working example. I have just trying to set the Application data container local setting values from the WPF side.

    But even a BUTTON click event not firing from WPF Side(if setting local setting values from WPF).
    So is it possible to set UWP Application Setting values from a full trusted WPF app?

    Like

    1. The answer to your question is yes, this should just work. I can’t diagnose what’s wrong from your short description and I don’t understand how button click events related to writing to app data. Obviously button click events work in WPF.

      For general support on Windows/UWP APIs I recommend your post your question on stackoverflow. Folks over there will be able to help you and the general public will benefit from see your question and the answers in a broader public forum.

      Like

Leave a comment