How to Manage Visualstates of Controls in WPF

VisualStates in Windows Presentation Foundation (WPF) are utilized to define various visual states that a control can exist in and to specify the visual modifications that should take place during the transition between these states. This feature enables developers to craft interactive and dynamic user interfaces that adapt to user interactions, alterations in application state, or other occurrences.

An Overview of Visual States in WPF

  • Defining visual states: Visual states are established using the VisualStateManager.VisualStateGroups attached property is typically included in the XAML markup of a control template. Within a VisualStateGroup, you outline one or more VisualState elements, each representing a unique visual state that the control can assume.
  • Transitions between states: Transitions between visual states are determined by the VisualState.Transitions property. These transitions specify the circumstances under which a transition should take place, such as when a property value changes or when a specific event is triggered.
  • State setters: Within each VisualState, Setter elements can be defined to specify the visual alterations to be implemented when the control enters that state. These setters commonly target properties like Background, Foreground, Visibility, Opacity, and more.
  • Visual state manager (VSM): The VisualStateManager class offers static methods for transitioning controls between visual states programmatically. This functionality enables you to initiate state changes in response to user input or other application events from the code behind.

The VisualStateManager is utilized in the given example to specify the visual states and transitions directly within the XAML markup of the control template. This method is appropriate for straightforward situations where the visual states can be defined statically in XAML.

Nevertheless, there are instances where it may be necessary to dynamically control visual states from the code behind. For instance, you may need to trigger a state change based on complex logic, user input, or changes in the application state that cannot be easily expressed in XAML alone.

In such scenarios, you can indeed utilize the VisualStateManager class from the code behind to programmatically manage visual states. The VisualStateManager offers methods like GoToState, which enable you to transition control to a specific visual state based on certain conditions.

Example

private void rect_MouseEvent(object sender, MouseEventArgs e)
{
    if (rect.IsMouseOver)
    {
        VisualStateManager.GoToElementState(rect, "MouseEnter", true);
    }
    else
    {
        VisualStateManager.GoToElementState(rect, "MouseLeave", true);
    }
}

Triggers

Visual states can also be activated using triggers, which automatically transition the control to a specific state based on property values or events. For instance, EventTrigger, DataTrigger, or Trigger can be utilized to trigger state changes according to mouse events, data modifications, or property values.

************************Implementation******************************

Step 1. I have developed a sample project for it as shown below.

Developed a sample project

Step 2. Define style like below

<Window.Resources>
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="Red" Duration="0" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="Green" Duration="0" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="Border" Background="LightGray" BorderBrush="Black" BorderThickness="1">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="TxtBox" TargetType="TextBox">
        <Setter Property="BorderBrush" Value="Gray"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="MinWidth" Value="120"/>
        <Setter Property="MinHeight" Value="30"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TextBox">
                    <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Red" Duration="0:0:0.2"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="MouseLeave">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Green" To="DarkBlue" Duration="0:0:0.2"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Green" Duration="0:0:0.2"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Yellow" Duration="0:0:0.2"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="TextChanged">
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Green" To="OrangeRed" Duration="10:10:0.2"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ScrollViewer x:Name="PART_ContentHost"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Step 3. XAML view

<Window x:Class="VisualStatesExample.MainWindow"
        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"
        xmlns:local="clr-namespace:VisualStatesExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="CustomButtonStyle" TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="Red" Duration="0" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="Green" Duration="0" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border x:Name="Border" Background="LightGray" BorderBrush="Black" BorderThickness="1">
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="TxtBox" TargetType="TextBox">
            <Setter Property="BorderBrush" Value="Gray"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="MinWidth" Value="120"/>
            <Setter Property="MinHeight" Value="30"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Red" Duration="0:0:0.2"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseLeave">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Green" To="DarkBlue" Duration="0:0:0.2"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Focused">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Green" Duration="0:0:0.2"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Gray" To="Yellow" Duration="0:0:0.2"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="TextChanged">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" From="Green" To="OrangeRed" Duration="10:10:0.2"/>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <ScrollViewer x:Name="PART_ContentHost"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Rectangle Name="rect"
                   Width="100" Height="100"
                   MouseEnter="rect_MouseEvent"
                   MouseLeave="rect_MouseEvent">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="MouseStates">
                    <VisualState Name="MouseEnter">
                        <Storyboard>
                            <ColorAnimation To="Green" 
                                            Storyboard.TargetName="rectBrush"
                                            Storyboard.TargetProperty="Color"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState Name="MouseLeave">
                        <Storyboard>
                            <ColorAnimation To="Red" 
                                            Storyboard.TargetName="rectBrush"
                                            Storyboard.TargetProperty="Color"/>
                        </Storyboard>
                    </VisualState>
                    <VisualStateGroup.Transitions>
                        <VisualTransition To="MouseLeave" GeneratedDuration="00:00:00.5">
                            <VisualTransition.GeneratedEasingFunction>
                                <ExponentialEase EasingMode="EaseOut" Exponent="10"/>
                            </VisualTransition.GeneratedEasingFunction>
                        </VisualTransition>
                        <VisualTransition To="MouseEnter" GeneratedDuration="00:00:00.5">
                            <VisualTransition.GeneratedEasingFunction>
                                <ExponentialEase EasingMode="EaseOut" Exponent="10"/>
                            </VisualTransition.GeneratedEasingFunction>
                        </VisualTransition>
                    </VisualStateGroup.Transitions>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Rectangle.Fill>
                <SolidColorBrush x:Name="rectBrush" Color="Red"/>
            </Rectangle.Fill>
        </Rectangle>
        <StackPanel>
            <TextBox Style="{StaticResource TxtBox}" BorderThickness="3" Height="40"   HorizontalAlignment="Stretch" Margin="10"/>
            <TextBox Text="Disabled TextBox" IsEnabled="False" HorizontalAlignment="Stretch" Margin="10"/>
        </StackPanel>
        <StackPanel VerticalAlignment="Bottom" >
            <Button Content="Custom Button" Style="{StaticResource CustomButtonStyle}" Width="150" Height="50"/>
        </StackPanel>
    </Grid>
</Window>

Main window

Code behind implementation

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace VisualStatesExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void rect_MouseEvent(object sender, MouseEventArgs e)
        {
            if (rect.IsMouseOver)
            {
                VisualStateManager.GoToElementState(rect, "MouseEnter", true);
            }
            else
            {
                VisualStateManager.GoToElementState(rect, "MouseLeave", true);
            }
        }
    }
}

Repository path: https://github.com/OmatrixTech/VisualStatesExample