Target window storyboard from button event (sounds too simple aye) - c#

I have complicated animation, which has to run at startup and every time when mouse leaves buttons (I have many buttons)
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard x:Name="storyboard">
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- I have to do this for every button -->
<EventTrigger RoutedEvent="MouseEnter" SourceName="button1">
<SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
<PauseStoryboard BeginStoryboardName="storyboard"/>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave" SourceName="button1">
<ResumeStoryboard BeginStoryboardName="storyboard"/>
</EventTrigger>
</Window.Triggers>
Instead of creating new animations I reuse just one, which was played in Loaded, this is why here Seek.., Pause.. and ResumeStoryboard.
Everything works fine, but there is a lot of duplicated code.
I started thinking of making a style for buttons to avoid WET xaml coding. At first, I tried this (simply to see if idea is good)
<Button x:Name="button1">
<Button.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
...
Not very surprisingly I got an exception
System.Windows.Media.Animation Warning: 6 : Unable to perform action because the specified Storyboard was never applied to this object for interactive control.; Action='Seek'; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='56868664'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; TargetElement='System.Windows.Controls.Button'; TargetElement.HashCode='23765798'; TargetElement.Type='System.Windows.Controls.Button'
I have feeling it should be something to do with Storyboard.Target..., but trying to give window name produces same error
<Window x:Name="window"
...
<SeekStoryboard BeginStoryboardName="storyboard" Offset="0" Storyboard.TargetName="window"/>
What should I do? My aim is to have button style (defined in window resources), but I have problem to target storyboard within window triggers from child control event trigger.

I understand that you have many buttons. So the Storyboard in Window.Triggers should be used for all the buttons (animate once when the window is loaded). However the Storyboard requires a specific target, how could you set it for all the buttons? We can only duplicate the code and target each one by its name. That's the first problem.
The second problem appears while attempting to solve the first problem. When placing all the SeekStoryboard, PauseStoryboard, ... inside a Button's Style, it cannot find the Storyboard's name in the Style scope. That means the only way to fix this is place the Storyboard right inside the Button's Style. But then we cannot access this Storyboard easily inside Window.Triggers. However to solve the first problem we should also not place the Storyboard inside Window.Triggers scope. Why don't we trigger the Storyboard when the Button's Loaded instead? Then we can place all inside a Button's Style. The following code should solve all your problems:
<Window.Resources>
<Storyboard x:Key="sb">
<!-- more code here ... -->
</Storyboard>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Visibility,
RelativeSource={RelativeSource AncestorType=Window},
Mode=OneTime}" Value="Visible">
<DataTrigger.EnterActions>
<StaticResource ResourceKey="beginSb"/>
</DataTrigger.EnterActions>
</DataTrigger>
<EventTrigger RoutedEvent="MouseEnter">
<SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
<PauseStoryboard BeginStoryboardName="storyboard"/>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<ResumeStoryboard BeginStoryboardName="storyboard"/>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

Related

EventTriggers on an Image

I have a set of Images in a GridView, and the images are not able to load instantly when the page is opened. So, to create a smoother transition, I am trying to use an EventTrigger in the Image to animate the opacity from 0 to 1 when the image loads, like so:
<GridView.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource ButtonBackground}" HorizontalAlignment="Stretch" MaxWidth="300" MinWidth="200">
<Image Source="{Binding SmallUri}" Stretch="UniformToFill"
Opacity="0" ToolTipService.ToolTip="{Binding Author.Name}">
<Image.Triggers>
<EventTrigger RoutedEvent="Image.ImageOpened">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="00:00:00.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
But whenever the page attempts to load, it crashes with the error:
Failed to assign to property 'Windows.UI.Xaml.EventTrigger.RoutedEvent'
Why does this not work? It also fails if I change the RoutedEvent property to Loaded, FrameworkElement.Loaded, Image.Loaded, or any other value I could think of. I would like a solution that does not involve having to write a custom control / codebehind.
But whenever the page attempts to load, it crashes with the error "Failed to assign to property 'Windows.UI.Xaml.EventTrigger.RoutedEvent'." Why does this not work?
As #Clemens said, problem is in Windows Runtime XAML, the default behavior for event triggers and the only event that can be used to invoke an EventTrigger is FrameworkElement.Loaded.
I'm writing this answer here to call another problem of your code, as you said:
It also fails if I change the RoutedEvent property to Loaded, FrameworkElement.Loaded, Image.Loaded
This is because you didn't specify the Storyboard.TargetName in your DoubleAnimation, it will throw the exception when it runs. To solve this problem, you will need to give a name to your Image in the DataTemplate and modify your storyboard like this:
<Image x:Name="myImage" Source="{Binding SmallUri}" Stretch="UniformToFill">
<Image.Triggers>
<EventTrigger RoutedEvent="Image.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="00:00:5" Storyboard.TargetName="myImage" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
From the Remarks section on the EventTrigger page on MSDN:
If you do choose to use Triggers, in Windows Runtime XAML, the default
behavior for event triggers and the only event that can be used to
invoke an EventTrigger is FrameworkElement.Loaded. Because that's both
the default and the only enabled behavior, don't set the RoutedEvent
attribute. Just use the XAML <EventTrigger>. For more info, see
Triggers.
I know it's too late for answering the question but for those who will be having the same issue.
you can use this XAML code:
<Image x:Name="TumbImage" Opacity="0" ...>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ImageOpened">
<media:ControlStoryboardAction Storyboard="{StaticResource ImageOpacityStoryBoard}" ControlStoryboardOption="Play"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
The Sample storyboard I used here:
<Storyboard x:Key="ImageOpacityStoryBoard">
<DoubleAnimation Storyboard.TargetName="TumbImage"
Storyboard.TargetProperty="Opacity"
From="0" To="1" Duration="0:0:0.33" />
</Storyboard>
and the XML namespaces used:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:media="using:Microsoft.Xaml.Interactions.Media"

Dynamically changing value of a property specified in a style in app.xaml

I am new to WPF. Recently i encountered an issue where i have to dynamically change the font size of a label.
To achieve this i created a test application and created a style with a key in my App.xaml.
Then gave this style to the label.
Style is as below:
<Style x:Key="myLbl" TargetType="Label">
<Setter Property="FontSize" Value="20"/>
</Style>
Then I gave this Style to the Label in my Window XAML like :
<Label Name="lblDemo" Content="Test" Foreground="Black" Style="{DynamicResource myLbl}"/>
I want to change the value of the Property in the Style with the click of the button. I searched for it a little bit but havent found anything useful. Can anyone suggest me a right possible direction. Any help will be appreciated.
Thanks.
If you'd like to do this using XAML only, you could do it like so:
<StackPanel>
<Label Name="lblDemo" Content="Test" Foreground="Black" FontSize="20"/>
<Button x:Name="ClickMe" Content="Enlarge" />
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="ClickMe">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="lblDemo" Storyboard.TargetProperty="FontSize">
<DiscreteDoubleKeyFrame KeyTime="00:00:00.01" Value="48" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</StackPanel.Triggers>
</StackPanel>
OR you can use good old fashioned Click event handler in code behind with this code (using the Button above without StackPanel.Triggers section):
private void ClickMe_OnClick(object sender, RoutedEventArgs e) { lblDemo.FontSize = 48; }

How to create glowing TextBox in WPF 4.0?

I'm trying to create a text box, which would glow when focused.
All samples of how to do this I've seen so far were based on OuterGlowBitmapEffect , and it appears that it does not work in .net 4.
The recommendation in the second article is to use blur effect. I have no idea on how to use blur to get object's outer layer to glow without distorting the inner content of the object.
Ultimately, I'm hoping to create a text box, which would display glow up animation when focused, and the glow would slowly (1-2 seconds) fade after the control has lost focus.
Any ideas on what is the best way to do this in wpf 4.0?
You can try to get a decent "Glow-Effect" with DropShadowEffect. Here is an example
Update. A TextBox that starts to "glow" when focused and the "glow" slowly fades out for two seconds when it loses focus
<TextBox Text="Test">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect ShadowDepth="0"
Color="Gold"
Opacity="0"
BlurRadius="8"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="1.0"
Storyboard.TargetProperty="(Effect).Opacity"
Duration="00:00:00"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0.0"
Storyboard.TargetProperty="(Effect).Opacity"
Duration="00:00:02"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Take a look at
http://wpfthemes.codeplex.com/
for a lot of good wpf theme ideas. In particular, look at the theme for the textbox in Bureau Black. I think what you want is actually what they use for their 'mouseover' attribute, but it should be easy to change that to a focused property instead.

ControlTemplate Storyboard color animation problem

I have a problem with color animation. This is my source:
<Window.Resources>
<hedit:BrushToColorConverter x:Key="BrushToColorConverter" />
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate.Resources>
<Storyboard x:Key="buttonAnimIn">
<!-- Problem line -->
<ColorAnimation Storyboard.TargetName="bntBack" Storyboard.TargetProperty="Color" To="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource BrushToColorConverter}}" />
</Storyboard>
<Storyboard x:Key="buttonAnimOut">
<ColorAnimation Storyboard.TargetName="bntBack" Storyboard.TargetProperty="Color" To="Blue" />
</Storyboard>
<Storyboard x:Key="buttonAnimForegroundIn">
<ColorAnimation Storyboard.TargetName="btnFore" Storyboard.TargetProperty="Color" To="Blue" />
</Storyboard>
<Storyboard x:Key="buttonAnimForegroundOut">
<ColorAnimation Storyboard.TargetName="btnFore" Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</ControlTemplate.Resources>
<Border Name="border"
BorderThickness="1"
Padding="4,2"
BorderBrush="DarkGray"
CornerRadius="3">
<Border.Background>
<SolidColorBrush Color="Blue" x:Name="bntBack" />
</Border.Background>
<ContentControl HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}">
<ContentControl.Foreground>
<SolidColorBrush Color="Red" x:Name="btnFore" />
</ContentControl.Foreground>
</ContentControl >
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource buttonAnimIn}" />
<BeginStoryboard Storyboard="{StaticResource buttonAnimForegroundIn}" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource buttonAnimOut}" />
<BeginStoryboard Storyboard="{StaticResource buttonAnimForegroundOut}" />
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
The problem is:
Cannot convert the value in attribute 'Style' to object of type 'System.Windows.Style'. Cannot freeze this Storyboard timeline tree for use across threads. Error at object 'System.Windows.Controls.Button' in markup file 'HLSLEditor;component/mainwindow.xaml' Line 223 Position 25.
When using fixed colors it worked, but it cannot work with the Foreground color of the parent...
How do I do an animation to the foreground or background color?
Thanks!
You cannot freeze Bindings, you probably can get around this issue by declaring a color as a resource and then bind your Control's Background to it while using StaticResource in the animation.
e.g.
<Window.Background>
<SolidColorBrush Color="{DynamicResource Background}"/>
</Window.Background>
<Window.Resources>
<Color x:Key="Background">Green</Color>
</Window.Resources>
<ColorAnimation Storyboard.TargetProperty="Foreground.Color"
Duration="0:0:1"
To="{StaticResource Background}"/>
Alternative using a resource class:
public static class MyColors
{
public static Color MyHighlightColor = Color.FromArgb(255, 0, 88, 0);
}
<ColorAnimation Storyboard.TargetProperty="Foreground.Color"
Duration="0:0:1"
To="{x:Static local:MyColors.MyHighlightColor}"/>
I think that understanding the error might give you a way of fixing the problem.
Animation requires the use of threads besides the UI thread. So storyboards have to be freezable, which means that all the animations in the storyboard must be freezable, and everything those animations use must also be freezable.
Bindings aren't freezable - pretty much by definition, as they are a mechanism whereby a dependency property can be changed. You can't use a dynamic binding in a color animation - there's the possibility that the property could change while the animation was running. The same thing happens whether you're binding to an object or you're using DynamicResource.
The thing is, this is protecting you from something that you don't really want anyway. You don't really want the colors to change while the animation is running. That's not what you're trying to accomplish. You want the color resources that the animation is using to change if the user selects a different skin.
So instead of binding storyboards to skinnable resources, add the storyboards to the dictionary of resources that get set when the skin changes (using static bindings to set the colors), and use dynamic binding in your event triggers. That should work.
When I came across this problem I worked around it by modifying my style to contain two identical elements on top of each other - one for the 'normal' state and one for the 'pressed' state. The 'pressed' one had its Opacity set to 0 by default and the other one had an Opacity of 1. My animation changed the opacities from 0 to 1 and vice versa.
This approach avoided actually animating the Color property but produced the same effect whilst keeping everything in XAML. As the colours were set in the style definition rather than the animation they could be bound as required. This will probably not be suitable for all situations but for my fairly simple style it was a very quick way to achieve the desired effect.

Controlling a UserControl's Storyboard from the parent Window in WPF using only XAML

I have created a very simple test app to try and solve this problem described to me by a co-worker. He was able to trigger it in C#, but I believe he needed the solution to be more generic and wanted it to be done strictly in the XAML. The problem was: How do you trigger the Storyboard inside of a UserControl to begin in response to an event on a control in the containing Window (the parent doesn't have to be a Window, it can be a Page or another UserControl...I use a Window for simplicity). So, my best example is that you have a Window with a Button and an instance of your UserControl. You want the Click event of the Button to trigger a Storyboard inside of the UserControl to begin.
Inside of the UserControl itself, the Storyboard is declared as a Resource with the key of MyStoryboard. I created a Button with an EventTrigger to show that I could trigger the Storyboard by referencing it with a Binding expression. Here is the complete XAML for my UserControl:
<UserControl x:Class="UserControlStoryboard.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<Storyboard x:Key="MyStoryboard">
<ColorAnimation Storyboard.TargetName="grid" Storyboard.TargetProperty="(Grid.Background).Color" From="CadetBlue" To="White" Duration="0:0:2" />
</Storyboard>
</UserControl.Resources>
<Grid Background="CadetBlue" x:Name="grid">
<Button Content="Press Me" Height="50">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" Path="Resources[MyStoryboard]" />
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
I was hoping that since I was able to tell a Storyboard to begin by referencing it with a Binding expression that I should be able to do the exact same thing from a Window that contains this UserControl as a child element. But I can't seem to figure out how to get a reference to the Storyboard inside of the parent Window. Is this impossible to accomplish in XAML only?
The parent window in my test project has nothing but a button and an instance of this UserControl. If it's even possible, which element would let me add an EventTrigger where the source of the event is the Button and the trigger action tells the UserControl's Storyboard to begin?
Thanks.
I think the idea of responding to external events within UserControl is not a very safe one. Within UserControl, you'd want to react only to events that are born within the control.
Can you flip the situation around and do the following: ?
Expose the UC property that needs to be animated as a dependency property (background in this case)
Create both EventTrigger and Storyboard on the parent window
You can access the storyboard by using the StaticResource. You need only assign the resource to the Storyboard on the BeginStroyboard.
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}">
</BeginStoryboard>
I changed the code to the following and when you press the button, it will start the storyboard for you. Hope this helps.
<UserControl.Resources>
<Storyboard x:Key="MyStoryboard">
<ColorAnimation Storyboard.TargetName="grid" Storyboard.TargetProperty="(Grid.Background).Color" From="CadetBlue" To="White" Duration="0:0:2" />
</Storyboard>
</UserControl.Resources>
<Grid Background="CadetBlue" x:Name="grid">
<Button Content="Press Me" Height="50">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}">
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
There probably isn't a good way to do it with pure Xaml.
You can:
Make a dependency property on your UserControl that takes a Button, then you can bind to it.
Make some kind of crazy converter that traverses your visual tree looking for something in particular.
Both require some C#, so I don't know if any of the above is what you are looking for.

Categories

Resources