Building a Windows Tray App by combining Microsoft.UI.Reactor and a Worker Project

I wanted a small Windows app that lives in the tray instead of the taskbar, and minimizes/closes to the tray. This can be useful for building small service apps, while having a UI you can occasionally access to control settings etc.

Here is the full process for setting that up step by step.

1. Start with a Worker project

I started with the standard worker template. In a new project folder, create a worker project using the worker template:

dotnet new worker

I liked the Worker template for this because a tray app is really a background app first. The process needs to stay alive even when the window is hidden.

2. Retarget the project for Windows

The next step was changing the target framework in the project file - we need this to be compatible with WinUI/Reactor and gives access to the APIs needed for windowing and tray behavior. Change the target framework in the project file to:

<TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>

3. Add the UI packages

After that I added the packages the app needs:

dotnet package add Microsoft.UI.Reactor --prerelease
dotnet package add WinUIEx

Microsoft.UI.Reactor gave me a clean C# way to define the window UI. WinUIEx helps with tray icon support and some of the window behavior.

4. Add the Windows app properties

Then I added a few properties in the project file needed to compile WinUI and Reactor apps:

<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<Platforms>ARM64;X64</Platforms>

5. Add the tray icon asset

The app needs a real .ico file for the tray. I added trayicon.ico file to the project and marked it as content:

<Content Include="trayicon.ico" />

6. Keep the app entry point simple

Program.cs stays very small:

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();

7. Create the Settings Window:

Create a new SettingsWindow.cs file with the UI. We'll just use the simple Reactor sample window here as a starting point:

using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Xaml.Controls;
using WinUIEx;
using static Microsoft.UI.Reactor.Factories;

namespace ReactorTrayWorker;

internal sealed class SettingsWindow : Component
{
    public override Element Render()
    {
        var (name, setName) = UseState("World");

        return 
            VStack(
            Heading($"Hello, {name}!"),
            TextBox(name, setName, placeholderText: "Your name")
                .AutomationName("NameInput")
        ).Padding(16);
    }
}

You of course want to change this to whatever content you want here.

8. Create a static UI Window creator for Reactor

Create an initialize method for WinUI/Reactor that takes an action for quitting (we'll use that later):

internal static void InitializeWinUIWindow(Action stopHost)
{
    ReactorApp.Run(_ =>
    {
        ReactorWindow? settingsWindow = null;
        settingsWindow = ReactorApp.OpenWindow(
            new WindowSpec
            {
                Title = "My Worker Settings",
                Width = 560,
                Height = 460,
                ActivateOnOpen = false,
            },
            () => new SettingsWindow(),
            configure: host =>
            {
                WindowManager manager = WindowManager.Get(host.Window);
                manager.WindowStateChanged += (_, state) =>
                {
                    // If the window is minimized, we don't want it to show in the Alt+Tab switcher + taskbar, so we set IsShownInSwitchers to false.
                    bool isVisible = state != WinUIEx.WindowState.Minimized;
                    manager.AppWindow.IsShownInSwitchers = isVisible;
                };
            });
    });
}

The app also watches WindowStateChanged through WindowManager.

When the window is minimized, the app updates IsShownInSwitchers so it does not stay visible in Alt+Tab or the taskbar. When it is restored, that visibility can come back.

This keeps the behavior consistent. Close hides it. Minimize hides it. The tray icon is the main way back in.

Inside Worker.cs, the background service starts the window setup by calling this method:

SettingsWindow.InitializeWinUIWindow(hostApplicationLifetime.StopApplication);

That shutdown callback matters. The tray menu needs a clean way to stop the whole host when the user actually wants to quit, and we'll use it in the Quit context menu further down. Also note that this call is blocking as long as the Reactor app runs, so wrap that call in a Task.Run you don't await.

9. Create the tray icon during window setup

Inside the window host configuration, let's add a TrayIcon that points to trayicon.ico:

string executablePath = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName!;
TrayIcon icon = new(0, Path.Combine(executablePath, @"trayicon.ico"), "My Worker") {
    IsVisible = true
};

At that point the app is available from the tray even if the window is hidden, and we just need to hook clicking it up to opening the window. The tray icon handles the Selected event. When the user clicks the tray icon, the app activates the main window, brings it to the front, and makes sure it can appear in the switcher again. Here I'm declaring a static action that I'll reuse for the context menu later as well.

var showWindow = static() => {
    ReactorApp.PrimaryWindow?.Activate(); // Activate the window
    ReactorApp.PrimaryWindow?.NativeWindow.SetForegroundWindow(); // Bring to front
    ReactorApp.PrimaryWindow?.AppWindow.IsShownInSwitchers = true; //Show in switcher
};
icon.Selected += (_, _) => showWindow();

 

10. Add the tray menu

The tray icon also handles the ContextMenu (right-click) event. We'll add two actions:

  1. Open
  2. Quit

Open brings the settings window back and does the same as left-click. Quit is the only path that fully exits the app and shuts down the worker.

icon.ContextMenu += (_, e) =>
{
    MenuFlyout flyout = new();
    flyout.Items.Add(new MenuFlyoutItem() { Text = "Open" });
    ((MenuFlyoutItem)flyout.Items[0]).Click += (_, _) => showWindow();
    flyout.Items.Add(new MenuFlyoutItem() { Text = "Quit" });
    ((MenuFlyoutItem)flyout.Items[1]).Click += (_, _) =>
    {
        isQuitting = true;
        ReactorApp.PrimaryWindow?.Close();
        icon.Dispose();
        stopHost.Invoke(); // Shuts down the worker thread
    };
    e.Flyout = flyout;
};

This is the core behavior of the whole project. The window is not supposed to control app lifetime like a normal WinUI app does. The tray icon does that. So to prevent uses from exiting the app, we'll handle the closing event as well:

host.Window.AppWindow.Closing += (_, e) =>
{
    // Prevent closing out the window and just hide to tray instead unless we're quitting
    e.Cancel = !isQuitting;
    settingsWindow?.Hide();
};

So now clicking the X button does not shut down the app. It just sends the window back to the tray, unless the quitting flag was set from the Quit context menu.

Here's what the entire initalize method now looks like:

internal static void InitializeWinUIWindow(Action stopHost)
{
    ReactorApp.Run(_ =>
    {
        bool isQuitting = false;
        ReactorWindow? settingsWindow = null;
        settingsWindow = ReactorApp.OpenWindow(
            new WindowSpec
            {
                Title = "My Worker Settings",
                Width = 560, Height = 460,
                ActivateOnOpen = false,
            },
            () => new SettingsWindow(),
            configure: host =>
            {
                // Configure Tray icon
                string executablePath = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).DirectoryName!;
                TrayIcon icon = new(0, Path.Combine(executablePath, @"trayicon.ico"), "My Worker")
                {
                    IsVisible = true
                };
                var showWindow = static () => {
                    ReactorApp.PrimaryWindow?.Activate();
                    ReactorApp.PrimaryWindow?.NativeWindow.SetForegroundWindow();
                    ReactorApp.PrimaryWindow?.AppWindow.IsShownInSwitchers = true;
                };
                // Left click on the tray icon will show the window:
                icon.Selected += (_, _) => showWindow();

                // Context menu options:
                icon.ContextMenu += (_, e) =>
                {
                    MenuFlyout flyout = new();
                    flyout.Items.Add(new MenuFlyoutItem() { Text = "Open" });
                    ((MenuFlyoutItem)flyout.Items[0]).Click += (_, _) => showWindow();

                    flyout.Items.Add(new MenuFlyoutItem() { Text = "Quit" });
                    ((MenuFlyoutItem)flyout.Items[1]).Click += (_, _) =>
                    {
                        isQuitting = true;
                        ReactorApp.PrimaryWindow?.Close();
                        icon.Dispose();
                        stopHost.Invoke();
                    };
                    e.Flyout = flyout;
                };

                // Prevent closing out the window and just hide to tray instead unless we're quitting
                host.Window.AppWindow.Closing += (_, e) =>
                {
                    e.Cancel = !isQuitting;
                    settingsWindow?.Hide();
                };

                // If the window is minimized, we don't want it to show in the Alt+Tab switcher + taskbar,
                // so we set IsShownInSwitchers to false when minimized.
                WindowManager manager = WindowManager.Get(host.Window);
                manager.WindowStateChanged += (_, state) =>
                {
                    bool isVisible = state != WinUIEx.WindowState.Minimized;
                    manager.AppWindow.IsShownInSwitchers = isVisible;
                };
            });
    });
}

And that's it! We now have the framework for a tray-based WinUI/Reactor application.

You can find the full application code here: https://github.com/dotMorten/ReactorExperiments/tree/main/TrayApp

File-based WinUI apps with Microsoft.UI.Reactor

One of the things I've been wanting to try for a while was whether Reactor could work nicely with .NET file-based apps.

Turns out: it can.

The idea is pretty simple. Instead of creating a full project, you put everything in a single App.cs file, add a few #: directives at the top, and run it directly with `dotnet run`.

The smallest example

Here's a complete Reactor app in one file:

#:property TargetFramework=net10.0-windows10.0.22621.0
#:property WindowsAppSDKSelfContained=true
#:package Microsoft.UI.Reactor@0.1.0-*

using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using static Microsoft.UI.Reactor.Factories;

ReactorApp.Run<App>("SingleFileReactor", width: 900, height: 600);

class App : Component
{
    public override Element Render()
    {
        var (name, setName) = UseState("World");

        return VStack(
            Heading($"Hello, {name}!"),
            TextBox(name, setName, placeholderText: "Your name")
                .AutomationName("NameInput")
        ).Padding(16);
    }
}

Save that as `App.cs`, then run: dotnet run App.cs -a x64

The -a x64 part matters here. Since the script uses WindowsAppSDKSelfContained=true, you need to tell the build which Windows architecture you want (you could also add #:property RuntimeIdentifier=win-x64 to the header instead)

And that's really it. You get a native desktop window with a heading and a text box, and as you type your name, the greeting updates live.

What the directives do

The three lines at the top do most of the magic:

#:property TargetFramework=net10.0-windows10.0.22621.0
#:property WindowsAppSDKSelfContained=true
#:package Microsoft.UI.Reactor@0.1.0-*

The target framework makes this a Windows app targeting WinUI.

WindowsAppSDKSelfContained=true makes sure the Windows App SDK bits are available the way the app expects.

And the package line is just a normal NuGet dependency, except declared inline for the file-based app model.

A normal Reactor app created from the project template is still the right place to start for a "real" app. You get a .csproj, a proper project structure, and something you can keep growing.

But file-based apps open up a different kind of scenario.

Sometimes you don't want to start a project. Sometimes you just want a little tool.

Maybe you want:

  • a quick internal utility
  • a tiny prototype
  • a one-off helper app
  • a script that sometimes needs a proper interactive UI

That's where this gets really fun.

Mixing scripting with a real UI

I often use file-based apps for running little scripts with C# instead of using Batch or Bash. This got me the idea that you can create a script that can either run in plain console mode, or pop a UI if you ask for interactive mode.

Here's a cleaned up version of that:

#:property TargetFramework=net10.0-windows10.0.22621.0
#:property WindowsAppSDKSelfContained=true
#:package Microsoft.UI.Reactor@0.1.0-*

using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Xaml;
using static Microsoft.UI.Reactor.Factories;

if (args.Length > 0 && (args[0] == "-i" || args[0] == "--interactive"))
{
    Console.WriteLine("Waiting for user to enter their name...");
    ReactorApp.Run<EnterUsernameApp>("Username", width: 300, height: 130);
}
else
{
    Console.WriteLine("Enter your user name:");
    AppState.Username = Console.ReadLine() ?? string.Empty;
}

if (string.IsNullOrEmpty(AppState.Username))
{
    Console.WriteLine("No username entered.");
    return -1;
}

Console.WriteLine($"Hello {AppState.Username}!");
return 0;

class EnterUsernameApp : Component
{
    public override Element Render()
    {
        var (name, setName) = UseState("");

        return VStack(
            TextBox(name, setName, placeholderText: "Enter username")
                .AutomationName("UsernameInput"),
            Button("Accept", () =>
                {
                    AppState.Username = name;
                    ReactorApp.PrimaryWindow?.Close();
                })
                .IsEnabled(!string.IsNullOrEmpty(name))
                .HAlign(HorizontalAlignment.Stretch)
        ).Padding(10);
    }
}

static class AppState
{
    public static string Username { get; set; } = string.Empty;
}

Run it in console mode: dotnet run App.cs -a x64
Or launch the UI mode: dotnet run App.cs -a x64 -- -i

If you're writing automation or a developer tool, that can be incredibly useful. Most of the time maybe the script can stay headless and work in the terminal. But if the user needs to make a choice, enter a value, or confirm something in a friendlier way, you can just pop a real window. Another example could be that you can either provide filenames as arguments, or if you don't provide this as argument, a UI allowing you to browse to the required files instead.

Is this how you should build every app?

Nope! Once the app grows beyond a quick tool or prototype, a normal project structure is still going to be much easier to maintain.

But for small utilities? Demos? Quick experiments? Interactive scripts, this is really nice.

I especially like that it lowers the bar for building little native Windows helpers. If I can keep a whole app in one file and still get a proper WinUI window, I'm far more likely to reach for a native UI instead of settling for a clunky prompt loop.

And that's probably the biggest compliment I can give this setup: it makes a real Windows UI feel cheap enough to use for the small stuff too.

Your first Microsoft.UI.Reactor app

Now that Microsoft.UI.Reactor and the project templates are on NuGet.org, getting started is a LOT simpler than it used to be.

If you want the absolute shortest path to a running app, it's really just this (assuming you already installed the .NET SDK):

dotnet new install Microsoft.UI.Reactor.ProjectTemplates
dotnet new reactorapp
dotnet run -a x64

That's it!

So let's take a quick look at what those three commands actually do, and more importantly what kind of app Reactor generates for you.

Creating the app

The first command installs the template:

dotnet new install Microsoft.UI.Reactor.ProjectTemplates

You only need to do this the first time.

Next:

dotnet new reactorapp

If you run that in an empty folder, the template uses the folder name as the project name and drops the generated files right there.

And finally:

dotnet run -a x64

That builds and launches the app with the x64 architecture, which is generally the right thing to do for a WinUI desktop app (if you are on ARM64 you can use arm64 instead).

If you prefer creating the app in a named folder instead, you can also do:

dotnet new reactorapp -n MyFirstReactorApp
cd MyFirstReactorApp
dotnet run -a x64

Project Contents

One of the nice things about the template is that it stays very small. You're not dropped into a huge starter app with a lot of moving parts. The interesting bits are basically just:

- App.cs
- <ProjectName>.csproj

And honestly, that's a pretty good first impression for Reactor, because the whole point is that your UI is just C# code.

Let's start with App.cs

The generated app looks like this:

using System;
using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using Microsoft.UI.Reactor.Layout;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using static Microsoft.UI.Reactor.Factories;

ReactorApp.Run<App>("MyFirstReactorApp", width: 900, height: 600);

class App : Component
{
  public override Element Render()
  {
    var (name, setName) = UseState("World");

    var titleBar = TitleBar("MyFirstReactorApp").Flex(shrink: 0);

    var body = Border(
      FlexColumn(
        Heading($"Hello, {name}!"),
        TextBox(name, setName, placeholderText: "Your name")
           .AutomationName("NameInput")
      ) with { RowGap = 16 }
    ).Padding(24).Flex(grow: 1, basis: 0);

    return FlexColumn(titleBar, body)
        .Backdrop(BackdropKind.Mica);
    }
}


This is pretty simple, but at the same time there's actually a lot going on here, so let's break down the basics.
The very first interesting line is this one:

ReactorApp.Run<App>("MyFirstReactorApp", width: 900, height: 600);

This is the app bootstrap. Reactor opens the window, hosts the app, and renders your root component. If you're used to XAML app startup plumbing, this is refreshingly simple.

Then we get to the App component itself:

class App : Component
{
    public override Element Render()

The Render methods are the key to Reactor. You don't create a window and start mutating controls. Instead you render an element tree that describes what the UI should look like right now, and it returns a light-weight description of the UI - not the UI objects themselves.

The next line is probably the most important one in the whole sample, and if you're coming from XAML probably the most unfamiliar one:

var (name, setName) = UseState("World");

UseState is one of many "hooks" you'll find in Reactor. You have some state. You render UI from that state. Events update the state. Reactor re-renders and patches the native WinUI controls in place.

The sample keeps this super simple: the initial value is `"World"`, so the heading renders as:

Heading($"Hello, {name}!")

which means the app starts out showing `Hello, World!` using a Textblock with the Heading style. The "Heading" is merely a simple short-cut for creating a TextBlock with the style preset, to keep your code simple and concise. There are a bunch of these short-hand statements like HStack and VStack for Horizontal and Vertical StackPanels.

Then the text box wires straight into that same state:

TextBox(name, setName, placeholderText: "Your name")

This is a nice little detail because it immediately shows the controlled-input model. As you type into the text box, `setName` gets called, the component renders again, and the heading updates live.

So type `Reactor`, and the heading becomes `Hello, Reactor!`.

That's a tiny app, sure, but it's also the whole Reactor loop in one screen.

The rest of the file is mostly layout and presentation.

var titleBar = TitleBar("MyFirstReactorApp").Flex(shrink: 0);

gives you a title bar, while:

FlexColumn(...)
Border(...).Padding(24)

build up the body using ordinary C# composition instead of XAML markup.

And finally:

.Backdrop(BackdropKind.Mica);

adds a Mica backdrop so the sample already feels like a proper Windows app.

That last part is worth calling out: Reactor isn't drawing some fake custom UI surface. It's building real WinUI UI under the covers.

The project file is small too

The generated project file is also pretty lean:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
    <Platforms>ARM64;X86;X64</Platforms>
    <UseWinUI>true</UseWinUI>
    <WindowsPackageType>None</WindowsPackageType>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="..." />
    <PackageReference Include="Microsoft.UI.Reactor" Version="..." />
  </ItemGroup>
</Project>

Again, nothing wild here, and that's a good thing.

UseWinUI makes this a WinUI project, WindowsPackageType is set to None so you start with an unpackaged desktop app, and Microsoft.UI.Reactor just comes in as a normal NuGet package.

That's really the big story here: Reactor now feels like a regular part of the .NET ecosystem. Install a template, create a project, run it. Edit it in Visual Studio, VS Code, or just Notepad for a bit of personal torture. Or have your favorite agent iterate on it. Because everything is code, you're more likely to get compile time errors instead of XAML runtime errors which really helps with both your and an agent's inner dev-loop.

What you get when it runs

When the app starts up, you get a small native desktop window with a title bar, a greeting, and a text box asking for your name.

Type in the box, and the heading updates immediately.

That might not sound like much, but it's actually a very good first sample because it teaches the right thing right away: the UI is a function of state.

No XAML. No view models. No binding setup. Just state in, UI out.

A lot of starter templates try too hard to impress you. They throw in navigation, settings pages, MVVM layers, mock data, half a dozen folders, and a bunch of code you're supposed to delete later.

This one doesn't.

It gives you one component, one piece of state, one layout, and one interaction. Just enough to understand the model, and not so much that you have to reverse engineer the template before you can start building your own app.

And that's probably exactly what a first Reactor app should be.

Where to next?

If you want learn more start with the Build 2026 presentation which covers a lot of the basics:

Building WinUI Apps with C# First Patterns and AI Assisted Workflows

Next check out the official Reactor Documentation, but I'd like to call out a few key pages to study:

Next go study the samples. Here are a few I found interesting:

  • Reactor Gallery - Lots of great samples in one big demo app. Run this app and study each sample.
  • Validation Showcase - Great example of how to do even intricate input validation with very little code.
  • Reactor IDE - A Visual-Studio style app demonstrating docking/draggable windows.
  • Particle Storm - High-performance particle rendering using Win2D.

Using the C#/Win32 code generator to enhance your WinUI 3 app


With the new C#/WIn32 code generator it has become super easy to generate interop code for Windows APIs. Previously we had to go read the doc and figure out the proper C# signature, or browse pinvoke.net and hope it is covered there. We could also reference an external package like PInvoke.Kernel32, PInvoke.User32 etc. etc. but it would also pull in a lot more dependencies and APIs than you probably need.

Now with the code generator, it's as simple as adding the nuget package, and then add the method name you want to a "NativeMethods.txt" file, and you instantly have access to the method and any structs and enums you need. It also uses all the latest C#9 features for improved interop code.

You can read the full blogpost on this here: https://blogs.windows.com/windowsdeveloper/2021/01/21/making-win32-apis-more-accessible-to-more-languages/

 

Using the C# Win32 Interop Generator with WinUI 3

The latest WinUI 3 Preview 4 release does have several improvements around improving your window, but there is also a lot still missing. However most of those features can be added by obtaining the window's HWND handle and calling the native Win32 methods directly.

So let's set out to try and control our window using CsWin32.

 

First, we'll create a new WinUI Desktop project:

 

image

Next we'll add the Microsoft.Windows.CsWin32 NuGet package the project (not the package project!) to get the Win32 code generator. However, note that the current v0.1.319-beta version has some bugs that you'll see when WinUI is referenced. So instead we'll use the daily build which has been fixed (you can skip this if you're reading this and a new version has been published). Go edit the project file, and add the following to get the latest daily from the nuget server that hosts these builds: (also shout-out to Andrew Arnott for quickly fixing bugs as they were discovered)

  <PropertyGroup>
   <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/azure-public/vside/_packaging/winsdk/nuget/v3/index.json;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.370-beta">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

 

Next we'll add a new text file to the root of the project named NativeMethods.txt.

Screenshot 2021-02-19 100955

We're now set up to generate the required methods.


The first Win32 method we need is the ShowWindow method in User32.dll, so add the text ShowWindow to its own line in the NativeMethods.txt file.

Note that you should now be able to get auto completion and full intellisense documentation on the method Microsoft.Windows.Sdk.PInvoke.ShowWindow. Pretty cool huh?

Screenshot 2021-02-19 094339

If you use VS16.9+, you can also expand the Project Name -> Dependencies -> Analyzers -> Microsoft.Windows.CsWin32 -> Microsoft.Windows.CsWin32.SourceGenerator and see the code that gets generated.

Screenshot 2021-02-19 094156

None of this is coming from a big library. It's literally just an internal class directly compiled into your application. No external dependencies required, and if you're building a class library, no dependencies to force on your users either.

 

Controlling the WinUI Window

As you can see in the screenshot above, the first thing we need to do is provide an HWND handle. You can get this from the Window, but it's not obvious, as we need to cast it to an interface we define. Let's create a new static class called WindowExtensions to help us do this:

using System;
using System.Runtime.InteropServices;
using WinRT;

namespace WinUIWinEx
{
    public static class WindowExtensions
    {
        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("EECDBF0E-BAE9-4CB6-A68E-9598E1CB57BB")]
        internal interface IWindowNative
        {
            IntPtr WindowHandle { get; }
        }

        public static IntPtr GetWindowHandle(this Microsoft.UI.Xaml.Window window)
            => window is null ? throw new ArgumentNullException(nameof(window)) : window.As<IWindowNative>().WindowHandle;
    }
}

 

Now we want to call the ShowWindow method. If you read the documentation for this method: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow you'll see takes a number indicating the desired window state, like 0 for hide, 3 for maximize, 5 for show, 6 for minimize and 9 for restore.

        private static bool ShowWindow(Microsoft.UI.Xaml.Window window, int nCmdShow)
        {
            var hWnd = new Microsoft.Windows.Sdk.HWND(window.GetWindowHandle());
            return Microsoft.Windows.Sdk.PInvoke.ShowWindow(hWnd, nCmdShow);
        }

        public static bool MinimizeWindow(this Microsoft.UI.Xaml.Window window) => ShowWindow(window, 6);
        public static bool MaximizeWindow(this Microsoft.UI.Xaml.Window window) => ShowWindow(window, 3);
        public static bool HideWindow(this Microsoft.UI.Xaml.Window window) => ShowWindow(window, 0);
        public static bool ShowWindow(this Microsoft.UI.Xaml.Window window) => ShowWindow(window, 5);
        public static bool RestoreWindow(this Microsoft.UI.Xaml.Window window) => ShowWindow(window, 9);

 

Because these are all extension methods, this now enables us to make simple calls inside our Window class like this:

       this.MaximizeWindow();

  

The CsWin32 code generator just makes this really easy. I've taken all of this a few levels further and started a WinUIEx project on GitHub, where you can see the above code and much more, like setting your windows to be always-on-top or adding a tray icon to your app, so you can minimize to the tray.

You can find the repo on github here:

https://github.com/dotMorten/WinUIEx/