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