Search code examples
c#xamluwpuwp-xamlvisualstatemanager

UWP - StateTrigger in ListView.ItemTemplate


I'm having trouble adding ColorAnimation to VisualStateManager on my ListView ItemTemplate. The VisualStateManager doesn't seem to change its visual states.

What I'm trying to do here is start a StoryBoard that would start to smoothly change the Rectangle.Fill color on the ListViewItem, as soon as its underlying viewmodel's IsReady property value changes.

What am I doing wrong? And how to do this correctly (preferably without the pointless UserControl)?

Here's the XAML:

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind MyList}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:B">
                    <UserControl>
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="group">
                                    <VisualState x:Name="state1">
                                        <VisualState.StateTriggers>
                                            <StateTrigger IsActive="{x:Bind IsReady, Mode=OneWay}"/>
                                        </VisualState.StateTriggers>
                                        <Storyboard>
                                            <ColorAnimation Duration="0:0:1.8" To="Red" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rect" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Rectangle x:Name="rect" Fill="Blue" Width="20" Height="20" />
                        </Grid>
                    </UserControl>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Click="Button_Click">Test</Button>
    </Grid>
</Page>

Here's the code behind:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App1
{
    public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
    {
        protected NotifyPropertyChangedBase()
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged([CallerMemberName]string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class B : NotifyPropertyChangedBase
    {
        private bool isReady;
        public bool IsReady
        {
            get { return isReady; }
            set { if (isReady != value) { isReady = value; RaisePropertyChanged(); } }
        }
    }

    public sealed partial class MainPage : Page
    {
        public ObservableCollection<B> MyList { get; private set; } = new ObservableCollection<B>();

        public MainPage()
        {
            this.InitializeComponent();

            for (int i = 0; i < 10; i++)
            {
                MyList.Add(new B());
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyList[2].IsReady = !MyList[2].IsReady;
        }
    }
}

Solution

  • Here a UserControl is needed. Without it, we may get error as you've seen. To manage visual states, we need a Control subclass, however, Grid is not a Control subclass, it inherits from Panel.

    Visual states are sometimes useful for scenarios where you want to change the state of some area of UI that's not immediately a Control subclass. You can't do this directly because the control parameter of the GoToState(Control, String, Boolean) method requires a Control subclass, which refers to the object that the VisualStateManager acts upon.

    We recommend you define a custom UserControl to either be the Content root or be a container for other content you want to apply states to (such as a Panel). Then you can call GoToState(Control, String, Boolean) on your UserControl and apply states regardless of whether the rest of the content is a Control.

    For more info, please see Visual states for elements that aren't controls under Remarks of VisualStateManager Class.