1

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..

S2S2
  • 8,322
  • 5
  • 37
  • 65

2 Answers2

4

If you want to perform the animation purely in xaml then you could use Triggers instead of the VisualStateManager. The following should give you the behaviour you're after:

 <Border Name="TheBorder" BorderThickness="5"
            Margin="30" Padding="20" >
        <Border.Background>
            <SolidColorBrush x:Name="BackgroundBrush" Color="Transparent" />
        </Border.Background>
        <Border.Style>
            <Style TargetType="{x:Type Border}">
                <Style.Triggers>
                    <Trigger Property="IsEnabled" Value="True">
                        <Trigger.EnterActions>
                            <BeginStoryboard Name="FlashStoryboard">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                   Duration="0:0:1" RepeatBehavior="Forever">
                                        <ObjectAnimationUsingKeyFrames.KeyFrames>
                                            <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <SolidColorBrush Color="Red"/>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames.KeyFrames>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                        <Trigger.ExitActions>
                            <StopStoryboard BeginStoryboardName="FlashStoryboard"></StopStoryboard>
                        </Trigger.ExitActions>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Border.Style>
    </Border>
Richard E
  • 4,819
  • 1
  • 19
  • 25
3

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.

Richard E
  • 4,819
  • 1
  • 19
  • 25
  • Thanks for the answer. But, I want to trigger the IsEnabled property change values in the XAML and cannot do it in code behind due to some constraints.. Would it be possible to do it in XAML itself? – S2S2 Jun 05 '13 at 05:39
  • I have posted another answer that uses Triggers. That solution will not require any code behind. – Richard E Jun 05 '13 at 07:31
  • Thanks for the triggers based answer. I edited my question and was rather trying to use VisualStates from my view model. It is almost working, only problem is: by default when app. starts- IsEnabled is True and the Flashing does not start; although when IsEnabled after that changes Visual State also works OK (only on app. start Flashing visual state does not start flashing..!!) – S2S2 Jun 05 '13 at 08:33
  • The problem could be that the control has not finished initializing when the VisualState is set for the first time. To get round this you could hook up the Initialized event and set your state in the EventHandler. I will edit the answer to show how this could be done using attached properties. That way no code behind or ViewModel properties will be required. – Richard E Jun 05 '13 at 12:15
  • Thanks for the answer and explanation.. I will try that.. Can you please also help with the ability to revert to `Normal Visual State` or change the Border background to `Transparent` when the Border MouseDown happens? Is it possible for you to help with a `MouseDown` trigger in other answer as I think `MouseDown` trigger will not work together with `VisualStates`...Thanks a lot... – S2S2 Jun 05 '13 at 13:23