Search code examples
c#uwpcustom-controlsdependency-propertiesvisualstatemanager

UWP VisualStateManager.GoToState() Not Working


I've created a custom control (ToggleIcon) that acts as a substitute for CheckBox, and need to add an IsChecked property to it so it can be programmatically cleared. I tried to accomplish this by using VisualStateManager.GoToState(), but despite executing with no Exceptions, it doesn't change the state. As far as I can tell, it also always returns false. I've looked at several other questions on here that all say the VisualStateManager has to be in the root of the UserControl, but when I move it there nothing on the control shows up. I can't figure out where to place the VisualState XAML to make it work, does anyone have any ideas on how to fix this? Alternatively, is there another way to implement IsChecked and have the state update whenever it changes? I can update the IsChecked property when the control is clicked/tapped, but can't get it to work in reverse (i.e. the VisualState changes to Checked or Unchecked when IsChecked changes).

UserControl XAML:

<UserControl
    x:Class="CustomControlScratchpad.ToggleIcon"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CustomControlScratchpad"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="128"
    d:DesignWidth="128">    
    <Grid>
        <CheckBox Width="32" Height="32">
            <CheckBox.Template>
                <ControlTemplate TargetType="CheckBox">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Tapped="Border_Tapped">
                        <Grid>
                            <BitmapIcon x:Name="UncheckedState" UriSource="{Binding UncheckedImage }" Opacity="0" ShowAsMonochrome="False" />
                            <BitmapIcon x:Name="CheckedState" UriSource="{Binding CheckedImage}" Opacity="0" ShowAsMonochrome="False" />
                        </Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CheckStates">
                                <VisualState x:Name="Checked">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="CheckedState" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unchecked">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="UncheckedState" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Border>
                </ControlTemplate>
            </CheckBox.Template>
        </CheckBox>
    </Grid>
</UserControl>

UserControl code:

public bool IsChecked
        {
            get { return (bool)GetValue(IsCheckedProperty); }
            set { SetValue(IsCheckedProperty, value); }
        }

        public static readonly DependencyProperty IsCheckedProperty =
            DependencyProperty.Register("IsChecked", typeof(bool), typeof(ToggleIcon), new PropertyMetadata(false));

        public ImageSource UncheckedImage
        {
            get { return (ImageSource)GetValue(UncheckedImageProperty); }
            set { SetValue(UncheckedImageProperty, value); }
        }

        public static readonly DependencyProperty UncheckedImageProperty =
            DependencyProperty.Register("UncheckedImage", typeof(ImageSource), typeof(ToggleIcon), new PropertyMetadata(new System.Uri("ms-appx:///Assets/default-unchecked.png")));

        public ImageSource CheckedImage
        {
            get { return (ImageSource)GetValue(CheckedImageProperty); }
            set { SetValue(CheckedImageProperty, value); } }

        public static readonly DependencyProperty CheckedImageProperty =
            DependencyProperty.Register("CheckedImage", typeof(ImageSource), typeof(ToggleIcon), new PropertyMetadata(new System.Uri("ms-appx:///Assets/default-checked.png")));

        public ToggleIcon()
        {
            this.InitializeComponent();
            DataContext = this;
        }

        private void Border_Tapped(object sender, TappedRoutedEventArgs e)
        {
            IsChecked = !IsChecked;
        }

Test Button event code: For testing, I added a button that's supposed to toggle the VisualState from one to the other when IsChecked is changed.

        private void Button_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            //toggleIcon.IsChecked = !toggleIcon.IsChecked;
            if (toggleIcon.IsChecked)
                VisualStateManager.GoToState(toggleIcon, "Unchecked", false);
            else
                VisualStateManager.GoToState(toggleIcon, "Checked", false);
        }

Solution

  • Please remove the Duration="0" from the DoubleAnimation of the VisualState. The DoubleAnimation won't work in the VisualState with the Duration.