How do I handle local theme changes in WinUI3?

Viewed 51

I have an app that has a button to take a screenshot of a UserControl. I'd like the screenshot to appear as as if Application.Current.RequestedTheme = ElementTheme.Light, even when Application.Current.RequestedTheme == ElementTheme.Dark.

To do this, I am changing the requested theme of the UserControl, like this example:

XAML

<UserControl
    x:Class="TestWinUI3App.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <UserControl.Resources>
        <ResourceDictionary>            
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Default">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/>
                </ResourceDictionary>
                <ResourceDictionary x:Key="Light">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/> 
                </ResourceDictionary>
                <ResourceDictionary x:Key="Dark">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/>           
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>

    <StackPanel Orientation="Vertical">
        <Button Content="Switch theme" Tapped="Button_Tapped"/>
        <Border x:Name="Border" BorderThickness="1">
            <TextBlock Text="Theme text"/>
        </Border>
    </StackPanel>
</UserControl>

C#

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;

namespace TestWinUI3App
{
    public sealed partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            UpdateBrush();
        }

        private void Button_Tapped(object sender, TappedRoutedEventArgs e)
        {
            RequestedTheme = RequestedTheme == ElementTheme.Dark ? ElementTheme.Light : ElementTheme.Dark;
            UpdateBrush();
        }

        private void UpdateBrush()
        {
            Border.BorderBrush = Resources["BorderBrush1"] as SolidColorBrush;
        }
    }
}

Clicking the button successfully changes TextBlock controls from white to black as appropriate on the screenshots, but the border colour does not change.

If I set the border colour like this:

<Border x:Name="Border" BorderThickness="1" BorderBrush="{ThemeResource BorderBrush}">

It works, however this is not an option for the actual user control as the content is generated dynamically.

How do I do the equivalent of setting the colour to {ThemeResource BorderBrush} in codebehind?

I tried using a ThemeListener control, but it only seems to respond to theme changes at the app level.

2 Answers

Using TextFillColorPrimary directly switches the border color. This way you don't need to call UpdateBrush().

<UserControl
    x:Class="TestWinUI3App.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ThemeTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <!--<UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Default">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/>
                </ResourceDictionary>
                <ResourceDictionary x:Key="Light">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/>
                </ResourceDictionary>
                <ResourceDictionary x:Key="Dark">
                    <StaticResource x:Key="BorderBrush" ResourceKey="TextFillColorPrimaryBrush"/>
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>-->

    <StackPanel Orientation="Vertical">
        <Button Content="Switch theme" Tapped="Button_Tapped"/>
        <Border x:Name="Border" BorderThickness="1" BorderBrush="{ThemeResource TextFillColorPrimary}">
            <TextBlock Text="Theme text"/>
        </Border>
    </StackPanel>
</UserControl>

There is no backing class representation for the ThemeResource markup extension that the XAML processor uses but you should be able to get a reference to the theme dictionary and get the resource from there:

private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
    RequestedTheme = RequestedTheme == ElementTheme.Dark ? ElementTheme.Light : ElementTheme.Dark;
    UpdateBrush();
}

private void UpdateBrush()
{
    var themeDictionary = Resources.ThemeDictionaries[ActualTheme.ToString()] as ResourceDictionary;
    Border.BorderBrush = themeDictionary["BorderBrush"] as SolidColorBrush;
}
Related