Category Archives: WinUIEx

Multi-Windowing in WinUI 3

In this article we take a look at multi-windowing in a WinUI 3 Desktop application. The ability for an app to control multiple windows -possibly over multiple monitors- is a key differentiator to Web development. We’ll demonstrate

  • opening multiple windows in a WinUI 3 app,
  • messaging between components using MVVM Toolkit and Dependency Injection, and
  • controlling window properties using WinUIEx.

We built a sample WinUI 3 Desktop app. This is how it looks like on Windows 10:

Here’s the list of the Nuget packages that we installed in the project:

The app comes with some Pages displayed in two Window classes:

  • the default Main Window (it’s called Shell in our app) is opened when the app starts, and
  • a DetailWindow is opened with every click on the launch button on our home page.

Most WinUI apps only have a main window. It’s often populated with a NavigationView menu and a Frame to host the different page types. Our sample app has a similar main window, but it will allow opening extra detail windows. Here’s the XAML structure of that detail window: unsurprisingly a Window with a Page.

<Window x:Class="XamlBrewer.WinUI3.MultiWindow.Sample.Views.DetailWindow">
    <Page x:Name="Page">

        <Page.DataContext>
            <viewmodels:DetailPageViewModel />
        </Page.DataContext>

        <Grid x:Name="Root">
            <!-- ... -->
        </Grid>
    </Page>
</Window>

Opening extra windows

To open a new window, just instantiate one and call Activate(). Here’s the core code behind our launch button:

private void LaunchButton_Click(object sender, RoutedEventArgs e)
{
    DetailWindow window = new();
    // ..
    window.Activate();

    Unloaded += (sender, e) => { window.Close(); };
}

All Window instances run on their own foreground thread. When you close the main window, the app does not stop. The detail windows remain open and keep the app running. That might not always be the desired behavior. In our sample app, we ensure that the satellite detail windows are closed together with the home page. We scheduled their Close() in an Unloaded event handler on the home page. This is convenient, but don’t consider this a best practice. Using an event handler implies that the home page is holding references to the detail windows – which will NOT be garbage collected when you close them. There are better ways of messaging available; we’ll come to that a bit later.

Controlling window properties

If an app requires multi-window support, some of the windows will host tools, extra controls, or dashboards. You may want to control the size and behavior of these. In the current state of WinUI 3 this involves low level interop with framework dll’s and dealing with window handles. Fortunately, Morten Nielsen already did the heavy lifting and shared it in the WinUIEx project -a set of WinUI 3 extension methods on windowing, authentication, and testing.

Here’s how we control the size, position, and the command bar behavior of our detail windows. All of these are WinUIEx extension method calls:

DetailWindow window = new();
window.SetIsMaximizable(false);
window.SetIsResizable(false);
window.SetWindowSize(360, 240);
window.CenterOnScreen();

Messaging

With your models, view, and services possibly scattered around multiple windows, you may want to consider a decent messaging infrastructure to allow communication between these components. In our sample app we didn’t want to keep a central list of detail windows. However, we do need to send a set of messages between them, and also to and from the home page and main window.

MVVM Toolkit is a great choice for this task. Let’s use it in a couple of scenarios.

Window to Window communication

Our main window has a button to switch the Theme. We broadcast the theme change to all other windows in the app. MVVM Toolkit Messenger is the right component for this job.

First, we defined a message class for transporting the theme after a change – a ValueChangedMessage:

public class ThemeChangedMessage : ValueChangedMessage<ElementTheme>
{
    public ThemeChangedMessage(ElementTheme value) : base(value)
    {
    }
}

Via MVVM Toolkit base classes and interfaces, Models and ViewModels have multiple ways of getting access to the Messager service. Since they rely on inheritance, most of these ways are not available to Window instances. We called a little help from our friends Microsoft.Extensions.DependencyInjection and the MVVM Toolkit Ioc service provider helper.

When our app starts, a call to ConfigureServices() ensures that there’s an inversion-of-control container with a Messenger instance available to all components of the app:

Ioc.Default.ConfigureServices
    (new ServiceCollection()
        .AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
        .BuildServiceProvider()
    );

When the Theme changes, the main window fetches the Messenger instance with GetService() and broadcasts the message with Send(). It does not need to know if there are other windows or how much there are:

Ioc.Default.GetService<IMessenger>().Send(
    new ThemeChangedMessage(Root.ActualTheme)
);

Let’s take a look at the receiver. When a detail window instance starts, it approaches the Messenger to Register() a delegate to be called on an incoming ThemeChangedMessage:

Root.ActualThemeChanged += Root_ActualThemeChanged;

var messenger = Ioc.Default.GetService<IMessenger>();

messenger.Register<ThemeChangedMessage>(this, (r, m) =>
{
    Root.RequestedTheme = m.Value;
});

// Don't forget!
Closed += (sender, e) => { messenger.UnregisterAll(this); };

When a detail window is closed we call UnregisterAll() to ensure all further messages are ignored by it. It may take a while before it is garbage collected -especially with our Unloaded event handler in the home page- and we don’t want closed windows to crash our app.

Here’s the result of switching the theme from dark to light:

Here’s a second scenario for window-to-window communication. To visually keep track of the detail windows, our home page has a button that makes all of them move to the foreground. Here’s the message definition – a plain C# class without the need for a particular base class or interface:

public class RaiseMessage { }

Here’s the call behind the button on the home page:

private void RaiseButton_Click(object sender, RoutedEventArgs e)
{
    Ioc.Default.GetService<IMessenger>().Send(new RaiseMessage());
}

Here’s the registration and response of the detail windows – and an opportunity to demonstrate yet another useful WinUIEx extension:

messenger.Register<RaiseMessage>(this, (r, m) =>
{
    this.SetForegroundWindow();
});

This is how it looks like at runtime. Here’s a screenshot before the call:

And here’s the ‘after’ shot:

ViewModel to ViewModel communication

Our next use case is on ViewModel-to-ViewModel communication. We have more help from MVVM Toolkit, since this is right in its core business. Here’s the scenario: our detail windows are actually mining bitcoin diamonds. When diamonds are found, a NumberBox is filled for the quantity and a button pressed. The information will then be broadcasted to the ecosystem.

Here’s the core XAML structure of the page in the detail window:

<Page>
    <Page.DataContext>
        <viewmodels:DetailPageViewModel />
    </Page.DataContext>
    <Grid x:Name="Root">
        <!-- Some content omitted ... -->
        <RelativePanel VerticalAlignment="Top"
                        HorizontalAlignment="Right">
            <NumberBox x:Name="DiamondsBox"
                        Header="Diamonds"
                        Value="{x:Bind ViewModel.Diamonds, Mode=TwoWay}" />
            <Button RelativePanel.Below="DiamondsBox"
                    Command="{x:Bind ViewModel.DiamondsFound}">Collect</Button>
        </RelativePanel>
    </Grid>
</Page>

Here’s the message definition – a ValueChangedMessage that transports an integer:

public class AssetsChangedMessage : ValueChangedMessage<int>
{
    public AssetsChangedMessage(int value) : base(value)
    { }
}

Here’s how (part of) the detail page ViewModel looks like:

public partial class DetailPageViewModel : 
    ObservableRecipient
{
    [ObservableProperty]
    private int diamonds;

    public DetailPageViewModel()
    {
        DiamondsFound = new RelayCommand(DiamondsFound_Executed);

        Messenger.Register(this);
    }

    // This code will be generated by the field attribute.
    // public int Diamonds
    // {
    //     get => diamonds
    //     set => SetProperty(ref diamonds, value);
    // }

    public ICommand DiamondsFound { get; }

    private void DiamondsFound_Executed()
    {
        Messenger.Send(new AssetsChangedMessage(Diamonds));
    }
}

Via inheritance from ObservableRecipient, the ViewModel gets access to change notification helpers (from ObservableObject) and to the messaging infrastructure – a Messenger property. Notice that the diamonds field is decorated with ObservableProperty. This conveniently generates the corresponding getter and setter. Via an ICommand property that is backed up by a RelayCommand, the method that sends the message is bound to the button in the View – all according to the MVVM pattern.

Here’s the message registering and receiving part in the ViewModel of the home page:

public partial class HomePageViewModel : 
    ObservableRecipient, 
    IRecipient<AssetsChangedMessage>
{
    [ObservableProperty]
    private int wealth;

    public HomePageViewModel()
    {
        Messenger.Register(this);
    }

    // This method is auto-registered by implementing the interface.
    public void Receive(AssetsChangedMessage message)
    {
        Wealth += message.Value;
    }
}

This ViewModel also inherits from ObservableRecipient. On top of that it implements IRecipient<T>. This conveniently auto-registers the Receive<TMessage>() method to the corresponding incoming message type. The home page updates its stock – the Wealth property. This property is also generated through the ObservableProperty attribute on the field variable.

For the sake of completeness (and definitely also for fun) we added another ViewModel-to-ViewModel messaging scenario. When a detail ViewModel finds a diamond, all of its colleagues get excited for a few seconds. Here’s the corresponding code:

public partial class DetailPageViewModel : 
    ObservableRecipient, 
    IRecipient<AssetsChangedMessage>
{
    [ObservableProperty]
    private bool isExcited;

    public DetailPageViewModel()
    {
        DiamondsFound = new RelayCommand(DiamondsFound_Executed);

        Messenger.Register(this);
    }

    public void Receive(AssetsChangedMessage message)
    {
        IsExcited = true;
        CoolDown();
    }

    private void DiamondsFound_Executed()
    {
        Messenger.Unregister<AssetsChangedMessage>(this); // Don't react to own message.
        Messenger.Send(new AssetsChangedMessage(Diamonds));
        Messenger.Register(this);
    }
}

There’s no need to go into details here: it’s all stuff we just covered before. Here’s how the result looks like.

For the record: the animation in the home page is not Lottie based. Lottie is not yet available for WinUI 3. In mean time we have to help ourselves with animated GIFs.

Multi-windowing is an important feature in Windows development. WinUI 3 desktop apps that use multi-windowing will definitely benefit from the MVVM Toolkit Messenger and from the WinUIEx extension methods.

Our sample app lives here on GitHub.

Enjoy!