Search code examples
c#xamldata-bindingdatatemplatewinui-3

How to use x:Bind in a DataTemplate for a ToggleSwith.HeaderTemplate in WinUI 3


I'm trying to create a simple Style for the Header of all the ToggleSwitches on a given Page (WinUI 3 v1.2 desktop project). I'd really like to use x:Bind for all of my bindings (not Binding). Here's my Page.Resources

<Page.Resources>
    <Style TargetType="ToggleSwitch" >
        <Setter Property="FontSize" Value="{x:Bind app:App.ShellPage.RootShellFontSize, Mode=OneWay}" />
        <Setter Property="Foreground" Value="{x:Bind app:App.ShellPage.UiColorContentAreaForeground, Mode=OneWay}" />
        <Setter Property="HeaderTemplate">
            <Setter.Value>
                <DataTemplate x:DataType="ToggleSwitch">
                    <TextBlock Text="{x:Bind Header}" Foreground="{x:Bind Foreground}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

and the ToggleSwitch is defined as

<ToggleSwitch Grid.Row="1" Grid.Column="1" Header="Resize Elements" 
              OffContent="Don't Resize" OnContent="Resize Everything"
              IsOn="{x:Bind app:App.ShellPage.IsSettingsResizeElements, Mode=TwoWay}" />

I thought this would set the text in the Header TextBlock to whatever was set in the ToggleSwitch Header ("Resize Elements" here) and bind the Foreground of the Header to UiColorContentAreaForeground. Neither binding produces the expected results.

The first binding in the TextBlock, Text="{x:Bind Header}", always throws an error as the binding engine tries to cast the string "Resize Elements" to a ToggleSwitch prior to looking at the Header property. Using Text="{x:Bind}" does the same thing as does Text="{x:Bind OnContent}" or Text="{x:Bind OffContent}" (!?). I can't think why the latter two happen as ToString() returns the contents of Header.

The second binding in TextBlock binds to something but it has a different value than UiColorContentAreaForeground (although it's always the same wrong value).

Any idea what's wrong with the bindings, please? How should I write the bindings using x:Bind?

As an aside, using Text="{Binding}" works but I haven't found any form of binding that works for the Foreground property.


Solution

  • The first binding in the TextBlock, Text="{x:Bind Header}", always throws an error as the binding engine tries to cast the string "Resize Elements" to a ToggleSwitch prior to looking at the Header property.

    This is expected since you have set the Header of the ToggleSwitch to which your style is applied to this string value. The HeaderTemplate is applied to the Header.

    For your style to work, you should set the Header property of the ToggleSwitch to another ToggleSwitch which makes no sense.

    What you could do is to set the Header to a custom object like this:

    public class ToggleSwitchHeader
    {
        public string Header { get; set; }
        public Brush Foreground { get; set; }
    }
    

    XAML:

    <ToggleSwitch Grid.Row="1" Grid.Column="1" 
                  OffContent="Don't Resize" OnContent="Resize Everything">
        <ToggleSwitch.Header>
            <local:ToggleSwitchHeader Header="Resize Elements" Foreground="Red" />
        </ToggleSwitch.Header>
    </ToggleSwitch>
    

    Then you can change the TargetType of the DataTemplate to this type and use compiled bindings (x:Bind) in the template:

    <Style TargetType="ToggleSwitch" >
        <Setter Property="FontSize" Value="{x:Bind app:App.ShellPage.RootShellFontSize, Mode=OneWay}" />
        <Setter Property="Foreground" Value="{x:Bind app:App.ShellPage.UiColorContentAreaForeground, Mode=OneWay}" />
        <Setter Property="HeaderTemplate">
            <Setter.Value>
                <DataTemplate x:DataType="local:ToggleSwitchHeader">
                    <TextBlock Text="{x:Bind Header}" Foreground="{x:Bind Foreground}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>