microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.35k stars 678 forks source link

Can't override style of content within templated control #7792

Open michael-hawker opened 2 years ago

michael-hawker commented 2 years ago

Describe the bug

We're working on the new SettingsExpander control in Windows Community Toolkit Labs: https://github.com/CommunityToolkit/Labs-Windows/pull/253

As part of this we need to ensure that ToggleSwitch controls as part of the content get the proper styling to align to the design guidelines for this pattern:

image

Not this:

image

We need this to be done implicitly so developers don't have to add extra styling for each common option they add to their app. The style is part of using this contract in the control. We've done this in other places and scenarios, but this specific one is failing and we don't understand the differences in the setup here causing the issue.

Steps to reproduce the bug

Minimal repro (both UWP and WinUI 3):

<Page
    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">

    <Page.Resources>
        <Style x:Key="RightAlignedCompactToggleSwitchStyle"
           BasedOn="{StaticResource DefaultToggleSwitchStyle}"
           TargetType="ToggleSwitch">
            <Setter Property="Background" Value="Red"/>
        </Style>

        <Style x:Key="MyUserControl" TargetType="Button"> <!-- Target Type doesn't matter here, using as a makeshift Templated Control in XAML Studio -->
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <!-- For some reason template binding is negating the resource override here -->
                        <ContentPresenter Content="{TemplateBinding Content}">
                            <ContentPresenter.Resources>
                                <Style TargetType="ToggleSwitch" BasedOn="{StaticResource RightAlignedCompactToggleSwitchStyle}"/>
                            </ContentPresenter.Resources>
                        </ContentPresenter>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="MyUserControlHardcoded" TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <ContentPresenter>
                            <ContentPresenter.Content>
                                <ToggleSwitch/>
                            </ContentPresenter.Content>
                            <ContentPresenter.Resources>
                                <Style TargetType="ToggleSwitch" BasedOn="{StaticResource RightAlignedCompactToggleSwitchStyle}"/>
                            </ContentPresenter.Resources>
                        </ContentPresenter>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>

    <StackPanel Padding="40" Spacing="16">
        <!-- Doesn't Work as expected -->
        <Button Style="{StaticResource MyUserControl}">
            <ToggleSwitch/>
        </Button>
        <!-- Works within template -->
        <Button Style="{StaticResource MyUserControlHardcoded}"/>
        <!-- Works outside of a control -->
        <ContentPresenter>
            <ContentPresenter.Content>
                <ToggleSwitch/>
            </ContentPresenter.Content>
            <ContentPresenter.Resources>
                <Style TargetType="ToggleSwitch" BasedOn="{StaticResource RightAlignedCompactToggleSwitchStyle}"/>
            </ContentPresenter.Resources>
        </ContentPresenter>

        <!-- expected result -->
        <ToggleSwitch Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"/>
    </StackPanel>

</Page>

We also tried not using Template Binding and setting the content property of the inner ContentPresenter in OnApplyTemplate but that didn't work as expected either. (It's also not tied to ToggleSwitch as the content either, could be a Button just an example for our scenario.)

image

All controls should have red background.

Expected behavior

Style is properly overridden and picked up by the content of the control.

Screenshots

No response

NuGet package version

No response

Windows app type

Device form factor

Desktop

Windows version

Windows 10 (21H2): Build 19044

Additional context

Related other issues:

MikeHillberg commented 2 years ago

I think what's happening in the first case is that the ToggleSwitch has two parents: the Button (because it's set as Button.Content) and the ContentPresenter (because the binding causes it to also be ContentPresenter.Content). That creates ambiguity when walking up the tree to find an implicit style: which parent to follow? What happens with ContentControls like Button and ListView Item is a preference the templated parent (the Button over the ContentPresenter).

WinUI doesn't have API around this, but this is the same logical tree concept as in WPF (WinUI doesn't have the LogicalTreeHelper like WPF).

michael-hawker commented 1 year ago

Bumping, based on https://github.com/microsoft/microsoft-ui-xaml/discussions/8638