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

Time:12-01

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.

CodePudding user response:

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>

CodePudding user response:

Thank you @mm8! You had me at

The HeaderTemplate is applied to the Header.

Although this should be blindingly obvious, I kept getting stuck on the documentation for DataTemplate which says x:Bind uses the templated object as its root. I misinterpreted this to mean that all DataTemplates on the primary object (ToggleSwitch in this case) would refer to the primary object.

To clarify for those who are similarly code-challenged, as @mm8 points out, HeaderTemplate (and FooterTemplate too, I suppose) actually uses the object assigned to Header (Footer) whereas ContentTemplate would use the whole ToggleSwitch.

In this case, I assigned a String to Header so the proper code for the binding (using x:Bind) is

<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="x:String">
                    <TextBlock Text="{x:Bind}" Foreground="{x:Bind app:App.ShellPage.UiColorContentAreaForeground, Mode=OneWay}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

The TextBlock.Text is bound directly to the string object assigned to the ToggleSwitch.Header in the ToggleSwitch definition

<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}" />

Because a String doesn't have a Foreground property, I have to use an approach like that suggested by @mm8, or x:Bind directly to the same Source used in the Style Foreground Setter (which I show in the above solution).

Thanks again, @mm8, for the help as I struggle to understand the nuances of XAML in WinUI 3.

  • Related