I'm trying to create a simple Style
for the Header
of all the ToggleSwitche
s 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 theHeader
.
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 DataTemplate
s 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.