Search code examples
listviewuwpuwp-xamllistviewitem

How do I use the Reveal hover in a ListViewItem style with the Windows UI Library?


I'm trying to customize the default ListViewItem style for a control in our UWP application, but I can't get the Reveal hover/click effect to work.

Before adding the custom style, the Reveal effect works just fine (using a default ListView control). I right-click the ListView control in the Design view and select Edit Additional Templates -> Edit Generated Item Container (ItemContainerStyle) -> Edit a Copy... At this point the Style property is set to the newly generated style, but I haven't modified it yet so nothing should have changed in the UI. But the Reveal hover/click effect is now broken.

Our application uses the Windows UI Library (Microsoft.UI.Xaml) elsewhere, so <XamlControlsResources /> has been added to Application.Resources. If I remove this, the Reveal hover/click effect works. So it seems to be the combination of defining a custom style and adding the XamlControlsResources that breaks the effect. I've tried moving the style into App.xaml or a separate ResourceDictionary with no change. I've tried removing various portions of the style to determine what is causing it to break. If I remove the Setter for the Template it works, but that's precisely what I'm trying to customize.

According to Microsoft Docs, "It's important to note that Reveal needs both the brush and the setters in it's Visual State to work correctly. Simply setting a control's brush to one of our Reveal brush resources alone won't enable Reveal for that control. Conversely, having only the targets or settings without the values being Reveal brushes will also not enable Reveal." So presumably either the ListViewItemPresenter.RevealBackground property or the VisualStateManager is causing it to break, but I'm not sure how or why.

How can I define a custom style for my ListView without losing the hover effect (and without removing XamlControlsResources, since that is required by our application)?

Here's a simple repro:

App.xaml

<Application
    x:Class="BrokenRevealEffect.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    RequestedTheme="Dark">
    <Application.Resources>
        <!--  Remove XamlControlsResources to see the Reveal hover/click effect.  -->
        <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
    </Application.Resources>
</Application>

MainPage.xaml

<Page
    x:Class="BrokenRevealEffect.MainPage"
    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"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Page.Resources>
        <!--  This is the default style for the ListViewItem. It has not been modified.  -->
        <Style x:Key="abc" TargetType="ListViewItem">
            <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
            <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
            <Setter Property="Background" Value="{ThemeResource ListViewItemBackground}" />
            <Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}" />
            <Setter Property="TabNavigation" Value="Local" />
            <Setter Property="IsHoldingEnabled" Value="True" />
            <Setter Property="Padding" Value="12,0,12,0" />
            <Setter Property="HorizontalContentAlignment" Value="Left" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="MinWidth" Value="{ThemeResource ListViewItemMinWidth}" />
            <Setter Property="MinHeight" Value="{ThemeResource ListViewItemMinHeight}" />
            <Setter Property="AllowDrop" Value="False" />
            <Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
            <Setter Property="FocusVisualMargin" Value="0" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListViewItem">
                        <ListViewItemPresenter
                            x:Name="Root"
                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                            CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
                            CheckBrush="{ThemeResource ListViewItemCheckBrush}"
                            CheckMode="{ThemeResource ListViewItemCheckMode}"
                            ContentMargin="{TemplateBinding Padding}"
                            ContentTransitions="{TemplateBinding ContentTransitions}"
                            Control.IsTemplateFocusTarget="True"
                            DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
                            DragBackground="{ThemeResource ListViewItemDragBackground}"
                            DragForeground="{ThemeResource ListViewItemDragForeground}"
                            DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
                            FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
                            FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
                            FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
                            PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
                            PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
                            PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
                            PressedBackground="{ThemeResource ListViewItemBackgroundPressed}"
                            ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
                            RevealBackground="{ThemeResource ListViewItemRevealBackground}"
                            RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}"
                            RevealBorderThickness="{ThemeResource ListViewItemRevealBorderThemeThickness}"
                            SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"
                            SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
                            SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}"
                            SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}"
                            SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="Selected" />
                                    <VisualState x:Name="PointerOver">
                                        <VisualState.Setters>
                                            <Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
                                            <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}" />
                                        </VisualState.Setters>
                                    </VisualState>
                                    <VisualState x:Name="PointerOverSelected">
                                        <VisualState.Setters>
                                            <Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
                                            <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}" />
                                        </VisualState.Setters>
                                    </VisualState>
                                    <VisualState x:Name="PointerOverPressed">
                                        <VisualState.Setters>
                                            <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                            <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                        </VisualState.Setters>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <VisualState.Setters>
                                            <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                            <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                        </VisualState.Setters>
                                    </VisualState>
                                    <VisualState x:Name="PressedSelected">
                                        <VisualState.Setters>
                                            <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                            <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                        </VisualState.Setters>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="DisabledStates">
                                    <VisualState x:Name="Enabled" />
                                    <VisualState x:Name="Disabled">
                                        <VisualState.Setters>
                                            <Setter Target="Root.RevealBorderThickness" Value="0" />
                                        </VisualState.Setters>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                        </ListViewItemPresenter>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>
    <Grid>
        <!--  Remove ItemContainerStyle to see the Reveal hover/click effect.  -->
        <ListView ItemContainerStyle="{StaticResource abc}" ItemsSource="{x:Bind Items}" />
    </Grid>
</Page>

MainPage.xaml.cs

using System.Collections.Generic;
using Windows.UI.Xaml.Controls;

namespace BrokenRevealEffect
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();
        }

        public List<string> Items => new List<string> { "Red", "Yellow", "Blue", "Green" };
    }
}

Solution

  • I found that this can be resolved by repeating the definition of the RevealBackgroundBrush SystemControlTransparentRevealBackgroundBrush and ListViewItemRevealBackground with your custom style.

            <RevealBackgroundBrush
                x:Key="SystemControlTransparentRevealBackgroundBrush"
                FallbackColor="Transparent"
                TargetTheme="Light"
                Color="Transparent" />
            <StaticResource x:Key="ListViewItemRevealBackground" ResourceKey="SystemControlTransparentRevealBackgroundBrush" />
    

    Just add this code before the custom ListViewItem style in the Page.Resources or App.Resources or ResourceDictionary; wherever you define your styles.

    I have no logical explanation for why this is necessary. These resources are already defined in generic.xaml so this should not be necessary, but it does resolve the issue. I would chalk this up to a bug in the Windows UI Library.