Gess
Gess

Reputation: 469

C# WPF - Border Style Animations

I want to give a Border a button like behavior because I want to apply the asymmetric rounded edges and get rid of the focus blinking effect of the standard button. I want to apply different styles to it when hovering the mouse over it and when clicking it. It works well except for the transition between two styles. Consider the following XAML

<local:ClickableBorder local:FrameworkElementExt.AttachIsPressed="True" CornerRadius="5,10,5,10" BorderThickness="1" Height="27.334" Margin="154.667,31.333,223.667,0" VerticalAlignment="Top">
<local:ClickableBorder.Style>
    <Style TargetType="{x:Type local:ClickableBorder}">
        <Setter Property="BorderBrush" Value="Black"/>
        <Setter Property="Background" Value="LightGray"/>
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver"  Value="True"/>
                    <Condition Property="IsEnabled"  Value="True"/>
                </MultiTrigger.Conditions>
                <MultiTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Duration="0:0:0.200" To="White" Storyboard.TargetProperty="BorderBrush.Color"/>
                            <ColorAnimation Duration="0:0:0.200" To="DarkGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                        </Storyboard>
                    </BeginStoryboard>
                </MultiTrigger.EnterActions>
                <MultiTrigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Duration="0:0:0.200" To="Black" Storyboard.TargetProperty="BorderBrush.Color"/>
                            <ColorAnimation Duration="0:0:0.200" To="LightGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                        </Storyboard>
                    </BeginStoryboard>
                </MultiTrigger.ExitActions>
            </MultiTrigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="local:FrameworkElementExt.IsPressed"  Value="True"/>
                    <Condition Property="IsEnabled"  Value="True"/>
                </MultiTrigger.Conditions>
                <MultiTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Duration="0:0:0.200" To="DarkRed" Storyboard.TargetProperty="BorderBrush.Color"/>
                            <ColorAnimation Duration="0:0:0.200" To="Red" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                        </Storyboard>
                    </BeginStoryboard>
                </MultiTrigger.EnterActions>
                <MultiTrigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Duration="0:0:0.200" To="Black" Storyboard.TargetProperty="BorderBrush.Color"/>
                            <ColorAnimation Duration="0:0:0.200" To="LightGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                        </Storyboard>
                    </BeginStoryboard>
                </MultiTrigger.ExitActions>
            </MultiTrigger>
        </Style.Triggers>
    </Style>
</local:ClickableBorder.Style>

And the relevant code behind

public class FrameworkElementExt
{
    public static readonly DependencyProperty IsPressedProperty = DependencyProperty.RegisterAttached("IsPressed", typeof(bool), typeof(FrameworkElementExt), new PropertyMetadata(false));

    public static readonly DependencyProperty AttachIsPressedProperty = DependencyProperty.RegisterAttached("AttachIsPressed", typeof(bool), typeof(FrameworkElementExt), new PropertyMetadata(false, PropertyChangedCallback));

    public static void PropertyChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs args)
    {
        FrameworkElement element = (FrameworkElement)depObj;
        if (element != null)
        {
            if ((bool)args.NewValue)
            {
                element.MouseDown += new MouseButtonEventHandler(element_MouseDown);
                element.MouseUp += new MouseButtonEventHandler(element_MouseUp);
                element.MouseLeave += new MouseEventHandler(element_MouseLeave);
            }
            else
            {
                element.MouseDown -= new MouseButtonEventHandler(element_MouseDown);
                element.MouseUp -= new MouseButtonEventHandler(element_MouseUp);
                element.MouseLeave -= new MouseEventHandler(element_MouseLeave);
            }
        }
    }

    static void element_MouseLeave(object sender, MouseEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        if (element != null)
        {
            element.SetValue(IsPressedProperty, false);
        }
    }
    static void element_MouseUp(object sender, MouseButtonEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        if (element != null)
        {
            element.SetValue(IsPressedProperty, false);
        }
    }
    static void element_MouseDown(object sender, MouseButtonEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        if (element != null)
        {
            element.SetValue(IsPressedProperty, true);
        }
    }
    public static bool GetIsPressed(UIElement element)
    {
        return (bool)element.GetValue(IsPressedProperty);
    }
    public static void SetIsPressed(UIElement element, bool val)
    {
        element.SetValue(IsPressedProperty, val);
    }
    public static bool GetAttachIsPressed(UIElement element)
    {
        return (bool)element.GetValue(AttachIsPressedProperty);
    }
    public static void SetAttachIsPressed(UIElement element, bool val)
    {
        element.SetValue(AttachIsPressedProperty, val);
    }
}

public class ClickableBorder : Border

{
    public static readonly RoutedEvent ClickEvent;

    static ClickableBorder()
    {
        ClickEvent = ButtonBase.ClickEvent.AddOwner(typeof(ClickableBorder));
    }

    public event RoutedEventHandler Click
    {
        add { AddHandler(ClickEvent, value); }
        remove { RemoveHandler(ClickEvent, value); }
    }

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);
        CaptureMouse();
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        base.OnMouseUp(e);

        if (IsMouseCaptured)
        {
            ReleaseMouseCapture();
            if (IsMouseOver)
            {
                RaiseEvent(new RoutedEventArgs(ClickEvent, this));
            }
        }
    }
}

Now the code behind I have collected from some WPF tutorials I've read through as I don't yet have a firm grasp on the workings of attached properties and whatnot.

Basically the Border is now clickable and has an IsPressed property. The latter will be used as style trigger condition. So currently I have three style setter/animation pairs.

Default look:

<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Background" Value="LightGray"/>

MouseOver look:

<ColorAnimation Duration="0:0:0.200" To="White" Storyboard.TargetProperty="BorderBrush.Color"/>
<ColorAnimation Duration="0:0:0.200" To="DarkGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>

IsPressed look:

<ColorAnimation Duration="0:0:0.200" To="DarkRed" Storyboard.TargetProperty="BorderBrush.Color"/>
<ColorAnimation Duration="0:0:0.200" To="Red" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>

The problem now is that as soon as the Border is clicked and the IsPressed EnterActions and ExitActions are performed, the animations of the first MultiTrigger are no longer working. Obviously something with my Triggers, EnterActions and ExitActions is wrong but I don't know what it is.

Any advice is much appreciated!

Upvotes: 0

Views: 3895

Answers (1)

mm8
mm8

Reputation: 169150

Use a <RemoveStoryboard> as the exit action:

<Style TargetType="{x:Type local:ClickableBorder}">
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="Background" Value="LightGray"/>
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsMouseOver"  Value="True"/>
                <Condition Property="IsEnabled"  Value="True"/>
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
                <BeginStoryboard Name="sb">
                    <Storyboard>
                        <ColorAnimation Duration="0:0:0.200" To="White" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="DarkGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.EnterActions>
            <MultiTrigger.ExitActions>
                <RemoveStoryboard BeginStoryboardName="sb" />
            </MultiTrigger.ExitActions>
        </MultiTrigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="local:FrameworkElementExt.IsPressed"  Value="True"/>
                <Condition Property="IsEnabled"  Value="True"/>
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
                <BeginStoryboard Name="sb2">
                    <Storyboard>
                        <ColorAnimation Duration="0:0:0.200" To="DarkRed" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="Red" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.EnterActions>
            <MultiTrigger.ExitActions>
                <RemoveStoryboard BeginStoryboardName="sb2" />
            </MultiTrigger.ExitActions>
        </MultiTrigger>
    </Style.Triggers>
</Style>

Edit:

Ok, now the storyboard sets the colors correctly for each state. However, when the ExitAction is executed, the color now changes abruptly instead of animating. I want it to animate with some duration just as under the EnterActions. How can I do this?

Set the FillBehavior property of the exit Storyboards to Stop:

<Style TargetType="{x:Type local:ClickableBorder}">
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="Background" Value="LightGray"/>
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsMouseOver"  Value="True"/>
                <Condition Property="IsEnabled"  Value="True"/>
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <ColorAnimation Duration="0:0:0.200" To="White" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="DarkGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.EnterActions>
            <MultiTrigger.ExitActions>
                <BeginStoryboard>
                    <Storyboard FillBehavior="Stop">
                        <ColorAnimation Duration="0:0:0.200" To="Black" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="LightGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.ExitActions>
        </MultiTrigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="local:FrameworkElementExt.IsPressed"  Value="True"/>
                <Condition Property="IsEnabled"  Value="True"/>
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <ColorAnimation Duration="0:0:0.200" To="DarkRed" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="Red" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.EnterActions>
            <MultiTrigger.ExitActions>
                <BeginStoryboard>
                    <Storyboard FillBehavior="Stop">
                        <ColorAnimation Duration="0:0:0.200" To="Black" Storyboard.TargetProperty="BorderBrush.Color"/>
                        <ColorAnimation Duration="0:0:0.200" To="LightGray" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.ExitActions>
        </MultiTrigger>
    </Style.Triggers>
</Style>

Upvotes: 2

Related Questions