Search code examples
.netwpfbordervisualstatemanagervisualstates

How to create a Flashing and Normal Background Visual States for a Border control


How can I create 2 visual states for a WPF Border control : one that flashes the Background color between Transparent and Red; and one normal that sets the Border Background color back to Transparent and stops flashing?

Note: The WPF Border control is used inside the ContentTemplate of another control.

I also require them to be triggered when some property say IsEnabled of the Borderchanges from False to True and vice versa; and the IsEnabled property is bound to a ViewModel property. When we click on the Border-the Flashing should stop and the background should revert to normal..


Solution

  • You can define VisualStates with the VisualStateManager. To get the behaviour you want on the Border the following should be a good starting point:

    The xaml:

      <Border Name="TheBorder" BorderThickness="5"
                Margin="30" Padding="20"
                wpfApplication1:StateManager.VisualState="{Binding ElementName=TheBorder,
                                                                   Path=IsEnabled, Mode=TwoWay,
                                                                   Converter={StaticResource EnabledToVisualStateConverter}}">
            <Border.Background>
                <SolidColorBrush x:Name="BackgroundBrush" Color="Transparent"/>
            </Border.Background>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="Common">
                    <VisualState x:Name="Normal"/>
                    <VisualState x:Name="Flash">
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetName="BackgroundBrush" 
                                            Storyboard.TargetProperty="Color" To="Red"
                                            RepeatBehavior="Forever"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Border>
    

    The converter:

        public class EnabledToVisualStateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var isEnabled = (bool) value;
            if (isEnabled)
                return "Flash";
    
            return "Normal";
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    The StateManager class used to change the VisualState:

        public class StateManager
    {
        private static string _valueToApplyOnInitialization;
    
        public static readonly DependencyProperty VisualStateProperty =
            DependencyProperty.RegisterAttached("VisualState", typeof (string), typeof (StateManager),
                                                new PropertyMetadata(VisualStateChangeCallback));
    
        public static string GetVisualState(DependencyObject obj)
        {
            return (string)obj.GetValue(VisualStateProperty);
        }
        public static void SetVisualState(DependencyObject obj, string value)
        {
            obj.SetValue(VisualStateProperty, value);
        }
    
        public static void VisualStateChangeCallback(object sender, DependencyPropertyChangedEventArgs args)
        {
            var element = sender as FrameworkElement;
            if (element == null)
                return;
    
            if (!element.IsInitialized)
            {
                _valueToApplyOnInitialization = (String) args.NewValue;
                element.Initialized += OnElementInitialized;
            }
            else
                VisualStateManager.GoToElementState(element, (string)args.NewValue, true);
        }
    
        private static void OnElementInitialized(object sender, EventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element == null)
                return;
    
            VisualStateManager.GoToElementState(element, _valueToApplyOnInitialization, true);
            element.Initialized -= OnElementInitialized;
        }
    }
    

    If you want to use a property from your ViewModel rather than the IsEnabled property on your Border, then just replace the Binding to 'TheBorder' with your ViewModel property.