Monthly Archives: January 2021

Introducing the WinUI InfoBar control

According to its roadmap WinUI 3.0 is currently focusing on Win32 desktop applications and .NET 5. In mean time WinUI 2.x keeps on evolving, bringing XAML features and controls for UWP. One of these new controls is the InfoBar control which was released as a component of WinUI 2.5.

In its original proposal we read that Microsoft was looking for a uniform way to display “long-lived app-wide status messages for scenarios such as updates available or errors that may occur which prevent the user from experiencing an app or its features working as intended“.

Here’s an example of such an ‘update available’ message in Office:

OfficeStatusBar

Here’s another example of the type of message that InfoBar is designed to display: the ‘we are recording this session’ message in Teams. This is clearly more than just a notification, hence the control needs to be explicitly closed by the user:

RecordingStartedTeams

Unlike similar controls (notifications, teaching tip) the InfoBar is not a member of the Flyout family, so you should adapt your layout for it.

We made a little UWP sample app to play with this newcomer – and stretch it a little bit. Here’s how this app looks like, it demonstrates some typical usages of InfoBar:

Classics

The InfoBar API

Here’s an overview of InfoBar’s public interface:

InfoBar

The InfoBar controls displays a Message with a Title and an Icon (with background color and icon adapting to the Severity) and an optional ActionButton. You can take a look at its documentation here and its source code right here – it’s spread over a remarkably huge number of files.

Here’s an example of a XAML declaration in its simplest form – a warning without action button that displays a validation message of a text box:

<!-- Validation Panel -->
<winui:InfoBar Title="Not amused"
                Message="I think we need a recount ..."
                Severity="Warning"
                IsOpen="True" />

Here are some samples of typical declarations of an informational wide horizontal InfoBar at the top, with an action button (regular button or hyperlink):

<!-- Office 365 Status Bar -->
<winui:InfoBar Title="UPDATES ARE AVAILABLE"
                Message="Do you want to update this app right now? It will close and reopen."
                Severity="Informational"
                IsOpen="True">
    <winui:InfoBar.ActionButton>
        <Button Content="Update" />
    </winui:InfoBar.ActionButton>
</winui:InfoBar>

<!-- Visual Studio Status Bar -->
<winui:InfoBar Title="Performance report"
                Message="There's this weird extension slowing down the startup."
                Severity="Informational"
                IsOpen="True">
    <winui:InfoBar.ActionButton>
        <HyperlinkButton Content="Tell me more about this" />
    </winui:InfoBar.ActionButton>
</winui:InfoBar>

Here’s the Live Visual Tree for all these controls. Notice that all InfoBar instances live inside the XAML page. This means you need to preserve space for them:

LiveVisualTree

We also created an InfoBar with a WinUI DropDownButton as its ActionButton and IsClosable to false to force the user to select an option in the dropdown if she wants to close the message. Here’s the XAML:

<!-- One that Edge and Outlook should have -->
<winui:InfoBar x:Name="PreferencesInfoBar"
                Title="Preferences updated"
                Message="We changed your user preferences, you like it?"
                Severity="Informational"
                IsOpen="True"
                IsClosable="False">
    <winui:InfoBar.ActionButton>
        <winui:DropDownButton>
            <TextBlock FontFamily="Segoe MDL2 Assets"
                        FontSize="14"
                        Text="" />
            <winui:DropDownButton.Flyout>
                <MenuFlyout Placement="BottomEdgeAlignedLeft">
                    <MenuFlyoutItem Icon="Emoji2"
                                    Text="Keep"
                                    Click="MenuFlyoutItem_Click" />
                    <MenuFlyoutItem Text="Revert"
                                    Click="MenuFlyoutItem_Click">
                        <MenuFlyoutItem.Icon>
                            <FontIcon Glyph="" />
                        </MenuFlyoutItem.Icon>
                    </MenuFlyoutItem>
                    <MenuFlyoutItem Icon="Emoji"
                                    Text="Revert and never touch my preferences again"
                                    Click="MenuFlyoutItem_Click" />
                </MenuFlyout>
            </winui:DropDownButton.Flyout>
        </winui:DropDownButton>
    </winui:InfoBar.ActionButton>
</winui:InfoBar>

All menu items in the sample are hooked to the same handler that closes the InfoBar by setting the IsOpen property:

private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
    PreferencesInfoBar.IsOpen = false;
}

Here’s how it looks like in the sample app. Notice how the Live Visual Tree indicates that the menu flyout is hovering over the other controls – you don’t need to preserve space for these:

LiveVisualTree2

Here’s yet another typical example, but this time we created it programmatically. It’s an error message at the bottom of the page, much like the old WinForms StatusStrip:

var infoBar = new WinUI.InfoBar
{
    Title = "OOPS",
    Message = "Division by zero. Dude, I told you so ...",
    Severity = WinUI.InfoBarSeverity.Error,
    IsOpen = true,
    VerticalAlignment = VerticalAlignment.Bottom,
    Margin = new Thickness(0, 0, 0, 80)
};

RootGrid.Children.Add(infoBar);

The last of the ‘classical’ examples is an informational message at the right side of the page. We’re pretty close to a notification scenario here – except that the control still requires manual closing:

<!-- Toast (with ignored customizations) -->
<winui:InfoBar Title="WOOHOO"
                Message="Data has been saved properly."
                Severity="Success"
                IsOpen="True"
                CornerRadius="8"
                VerticalAlignment="Center"
                HorizontalAlignment="Right"
                Height="120">
    <winui:InfoBar.Content>
        <TextBlock Text="Keep up the good work ..."
                    Foreground="DimGray"
                    Padding="0 0 0 12" />
    </winui:InfoBar.Content>
</winui:InfoBar>

In the previous InfoBar instance, we tried to apply some customizations such as a specific Height and CornerRadius. The current control template just ignores these. That’s largely understandable, since the purpose of this new control is to bring a common look-and-feel for this type of scenario and steer away of custom solutions.

Customizing the InfoBar

On one hand it’s good to finally have a standard control for error and warning messages with a default look (size, color, icon) and a default behavior (requires explicit closing). We strongly believe that we should try to stick to this standard. On the other hand our customers very often require corporate UI and UX patterns that may deviate from the defaults.

Adapting the Template

It’s almost never a good idea to entirely re-template a control, especially since WinUI targets support across all Windows 10 versions. Hence its control templates may contain multiple IsApiContractPresent conditions so you’ll have to test your retemplated control for each and every version – and then you’re still not future-proof. On top of that you also have to consider accessibility when customizing controls.

There’s nothing wrong with lightweight styling (i.e. overriding some system brushes to sync the color scheme to your corporate style). WinUI controls facilitate this by making maximum use of XAML Theme Resources – you can check the ones that are used by the InfoBar right here. In the sample app we went a little bit further. We couldn’t resist to create a template for the InfoBar’s originally proposed look and feel:

Specials

we started with right-clicking on an instance in the Visual Studio designer, to create a copy of the current template:

CreateTemplate

You find the latest version of this template right here. It wouldn’t add value to post the source code of our template here, so we won’t. We’re happy with the outcome of this little exercise.

Adding Auto-Closing Behavior

The documentation is pretty clear on on where InfoBar should be used and where not. The control should e.g. NOT be used for just confirming an action, and in several GitHub issues –such as this one– it is stated that the control is NOT meant to be auto-closed. These are of course just recommendations, and we developers and our customers have been known to bypass recommendations sometimes, no?

In a lot of apps user interface consistency is –rightly- more important than following recommendations. Here’s an example of such consistency in Visual Studio. When you’re checking in code into a source control system (Azure DevOps, Git, whatever), the VS Team Explorer window displays an warning InfoBar as long as the action is busy, and an error InfoBar when something went wrong. After a successful check-in, the same UI is used for displaying the result:

GitHubVisualStudio

Most of us will agree that this behavior makes sense, but this last message is far from  “long-lived app-wide status messages for scenarios such as updates available or errors that may occur which prevent the user from experiencing an app or its features working as intended“.

It’s good to reuse the InfoBar default look here, but we believe that this last message deserves a different behavior: it should auto-close after a few seconds. That’s just what we did in the sample app. We created an InfoBar subclass with an extra public AutoCloseInterval and some private methods to implement the behavior (feel free to add a fancy Community Toolkit fade animation yourself). Here’s the class diagram for the control:

 AutoClosingInfoBar

Since the API of InfoBar is similar to the TeachingTip control, we were able to reuse the code for our own AutoClosingTeachingTip. The child class registers a notification function to listen for changes in the IsOpen property:

this.RegisterPropertyChangedCallback(IsOpenProperty, IsOpenChanged);

A timer is started:

private void Open()
{
    _timer = new DispatcherTimer();
    _timer.Tick += Timer_Tick;
    _timer.Interval = TimeSpan.FromMilliseconds(AutoCloseInterval);
    _timer.Start();
}

After the interval, the control is closed:

private void Timer_Tick(object sender, object e)
{
    this.IsOpen = false;
}

Allow us to repeat that with InfoBar it’s good to finally have a standard control for error and warning messages with a default look and a default behavior. We welcome this new member of the WinUI control family.

Our sample app lives here on GitHub.

Enjoy!