I hope someone is able to assist here, either to advise how to fix what I have or to suggest a better way of achieving what I'm after.
I have a simple custom control on which I want to be able to start/stop/pause/resume an opacity animation. I've looked at various solutions and figured that using EventTrigger
with a custom RoutedEvent
was the way forward. I can get the following to work with a standard event (eg MouseDown
) but am unable to get my custom event to start the storyboard.
I tried raising the event in the constructor prior to doing it in a loaded event, but neither makes any difference.
I don't think it has any relevant impact but this is also being hosted in an ElementHost
for winforms.
xaml:
<UserControl x:Class="Fraxinus.BedManagement.WpfControls.FraxIconX"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Fraxinus.BedManagement.WpfControls"
mc:Ignorable="d"
d:DesignHeight="16" d:DesignWidth="16"
Loaded="FraxIconX_Loaded">
<!--Height="16" Width="16" Background="{Binding Path=Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Control}, AncestorLevel=1}}" -->
<UserControl.Resources>
<ResourceDictionary>
<Border x:Key="RoundedBorderMask" Name="iconBorder" CornerRadius="4" Background="White" BorderThickness="2" Height="16" Width="16" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border BorderThickness="2" CornerRadius="4" Height="16" Width="16" HorizontalAlignment="Center" VerticalAlignment="Center">
<local:FraxCanvas x:Name="fraxCanvas" Height="16" Width="16" HorizontalAlignment="Center" VerticalAlignment="Center">
<local:FraxCanvas.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1" GradientStops="{Binding GradientStops}" />
</local:FraxCanvas.Background>
<local:FraxCanvas.Triggers>
<EventTrigger RoutedEvent="local:FraxCanvas.OnStartStoryboard" SourceName="fraxCanvas">
<BeginStoryboard x:Name="storyboard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0.25" AutoReverse="True" Duration="0:0:1" RepeatBehavior="Forever"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</local:FraxCanvas.Triggers>
<local:FraxCanvas.OpacityMask>
<!--https://wpf.2000things.com/2012/05/11/556-clipping-to-a-border-using-an-opacity-mask/-->
<VisualBrush Visual="{StaticResource RoundedBorderMask}"></VisualBrush>
</local:FraxCanvas.OpacityMask>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Height="16" Width="16" Source="{DynamicResource BitmapClockPng}" />
</local:FraxCanvas>
</Border>
</UserControl>
Code behind
public partial class FraxIconX : UserControl
{
public GradientStopCollection GradientStops { get; set; }
public FraxIconX() { }
public FraxIconX(Color backColour, int width = 16, int height = 16)
{
Width = width;
Height = height;
GradientStops = new GradientStopCollection { new GradientStop(Colors.AliceBlue, 0.0), new GradientStop(Colors.Navy, 1.0) };
Background = new SolidColorBrush(backColour);
DataContext = this;
InitializeComponent();
}
public void FraxIconX_Loaded(object sender, RoutedEventArgs args)
{
RaiseEvent(new RoutedEventArgs(FraxCanvas.StartStoryboardEvent));
}
}
FraxCanvas:
public class FraxCanvas : Canvas
{
public static readonly RoutedEvent StartStoryboardEvent = EventManager.RegisterRoutedEvent("OnStartStoryboard", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(FraxCanvas));
public event RoutedEventHandler OnStartStoryboard
{
add
{
this.AddHandler(StartStoryboardEvent, value);
}
remove
{
this.RemoveHandler(StartStoryboardEvent, value);
}
}
}
Two issues.
EventTrigger.SourceName="fraxCanvas"
, but the event is raised somewhere else (parent UserControl
).EventTrigger.SourceName
is a filter property, which allows to
exclusively handle a Routed Event of a specific element. If you don't set EventTrigger.SourceName
,
the trigger will handle the specified event, no matter the
source.FraxCanvas
element, because a parent raised it.RoutingStrategy.Bubble
: event travels from event source, the FraxIconX
element, to the
visual tree root e.g., Window
.RoutingStrategy.Tunnel
: event travels from the tree root e.g., Window
and stops at the event source, the FraxIconX
element.The solution is to remove the EventTrigger.SourceName
attribute and move the trigger to the event source (the UserControl
) or to any of the event source's parent element:
<UserControl>
<UserControl.Triggers>
<EventTrigger RoutedEvent="local:FraxCanvas.OnStartStoryboard">
<BeginStoryboard x:Name="storyboard">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="fraxCanvas"
Storyboard.TargetProperty="Opacity"
From="1" To="0.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</UserControl.Triggers>
<Border>
<FraxCanvas x:Name="fraxCanvas" />
</Border>
</UserControl>