I have a Style for a ToggleButton which defines a ControlTemplate. My ToggleButton is animated when it changes states, but i don't want it to animate when i navigate to a new page. So, i added an EventTrigger on the Loaded event with SkipStoryboardToFill to avoid this behavior, and it does what i want.
My only issue now, is that when i add a new ToggleButton, it tries to skip storyboards which haven't been started, generating an Animation Warning ("Unable to perform action because the specified Storyboard was never applied to this object for interactive control.") which seems to impact my application's performance.
I could probably work around that but i'd rather solve the actual problem. Is there a way i could add a condition in my EventTrigger ?
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Loaded">
<SkipStoryboardToFill BeginStoryboardName="checkedSB" />
<SkipStoryboardToFill BeginStoryboardName="uncheckedSB" />
</EventTrigger>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin">
<ThicknessAnimation To="20 1 2 1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="uncheckedSB">
<Storyboard Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin">
<ThicknessAnimation To="2 1 2 1" Duration="0:0:0.1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
Is there a way i could add a condition in my EventTrigger ?
Short answer: No.
An EventTrigger always applies when the corresponding event is being raised.
If you want to trigger the animations conditionally, you should either switch to using a MultiDataTrigger or implement the animations programmatically.
Related
I have this Style:
<Style x:Key="BlinkStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BlinkForError, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type loc:DevicesRepositoryEditorUserControl}}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="BlinkBeginStoryboard">
<Storyboard>
<ColorAnimation To="Red" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
FillBehavior="Stop" Duration="0:0:0.4" RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="BlinkBeginStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Whenever the bound dependency-property BlinkForError is set to True, it start blinking. It works great, like this:
<!-- When BlinkForError set to True, this TextBox, named "One", blinks: -->
<TextBox Name="One" Style="{StaticResource ResourceKey=BlinkStyle}"/>
Thing is that I want exactly the same thing, but bound to another dependency-property, say AnotherBlinkForError:
<!-- When AnotherBlinkForError set to True, this TextBox, named "Two", blinks: -->
<TextBox Name="Two" Style="{StaticResource ResourceKey=AnotherBlinkStyle}"/>
I can duplicate the whole style and only change the DataTrigger's Binding part.
Is there a way to avoid this duplication, reuse the same Style twice with two different bindings?
You could try and make use of the Tag properties on your TextBoxes and bind them to the BlinkForError and BlinkForAnotherError. In your style definition the binding will check the Tag value (you'd probably have to use RelativeSource and FindAncestor options) instead of the Blink properties.
But to be honest, if there are only two TextBoxes and respective error properties I would go with two separate styles as it's just less hassle.
I'm using a DataGrid to show some elements of a collection:
<DataGrid Name="grdItems" ItemsSource="{Binding Path=myItemsList}" [...]
what I want is that if a particular column of a Row is '1', that row starts to blink. I can achieve this behaviour in this way:
<DataGrid ... >
<DataGrid.Resources>
<Storyboard x:Key="rowBlink" x:Name="Blink" AutoReverse="True" RepeatBehavior="Forever" Timeline.DesiredFrameRate="40" SpeedRatio="1">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="00:00:01" Value="Orange" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</DataGrid.Resources>
</DataGrid>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Acked}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="blinkStoryBoard" Storyboard="{StaticResource rowBlink}" />
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Acked}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="blinkStoryBoard" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
The problem is that the animation are not in synch for each row of the grid. If a row is added later, it will blink not in phase with the others. Is there a way to synch the animation?
Not necessarily a solution to your exact question, but potentially a workaround that will achieve your intended effect:
Instead of having each row blink on its own, maybe you can have the background of the DataGrid blink? Those rows which shouldn't be animated can have their BackgroundColor set to your default color, and the animated rows can have their background set to Transparent (so you can see the background).
There may be a few issues, such as having edges of the DataGrid showing the BackgroundColor animation as it blinks, but it may be worth a try. I'd run it myself, but I don't have my environment set up on this machine...if I get a chance tonight I'll run a test and post here again.
I have the following simple animation:
<Window x:Class="AnimationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Background="Black">
<TextBox Name="Box" >
</TextBox>
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock" >
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation AutoReverse="True" To="#A933FF" Duration="0:0:1" Storyboard.TargetProperty="Foreground.Color" FillBehavior="Stop" IsCumulative="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
It just makes flash a value when the value changes. if you write a letter you see it flash correctly to the color set in the animation and go back. But if you click several times the duration is longer (which is the desired behavior) but then it goes to the original color without fading out. Why does this happen and how to avoid it?
So once again, we have a question where a user provides some code and says why is this happening? The answer in this case is the normal answer to these questions:
You wrote some code to make it happen
So to dig a little deeper, you have asked:
if you write a letter you see it flash correctly to the color set in the animation and go back. But if you click several times the duration is longer (which is the desired behavior) but then it goes to the original color without fading out. Why does this happen and how to avoid it?
First, why does this happen?
So the reason why it is happening is because you declared a ColorAnimation that has no From value set, so it will always start from the current value, whether this value has been manipulated by an Animation or not:
<ColorAnimation AutoReverse="True" To="#A933FF" Duration="0:0:1" FillBehavior="Stop"
Storyboard.TargetProperty="Foreground.Color" IsCumulative="True" />
For a single character entered, you'll see the ColorAnimation as you expected. However, when you continually type further characters, it will already have reached your set purple colour and you won't see any further animations until you stop typing, because it is now trying to animate from your purple colour to the same purple colour.
Now, how to avoid it?
To fix this issue, either supply a From colour, or set the Duration to be much quicker, or preferably both:
<ColorAnimation AutoReverse="True" From="White" To="#A933FF" Duration="0:0:0.1"
Storyboard.TargetProperty="Foreground.Color" FillBehavior="Stop"
IsCumulative="True" />
You can achieve the desired result using ColorAnimationUsingKeyFrames this will offer you much precise control over the same using keyframes instead of reversing the double animation which loses the initial value after being invoked multiple time and FillBehavior stop force it to revert to original value as result of animation complere
sample for you
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames Duration="0:0:2" Storyboard.TargetProperty="Foreground.Color" >
<SplineColorKeyFrame Value="#A933FF"/>
<SplineColorKeyFrame Value="White" KeyTime="0:0:1"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
more cleaner approach involves stopping the previous storyboard and invoking a new one
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock" >
<Style.Resources>
<Storyboard x:Key="animate">
<ColorAnimationUsingKeyFrames Duration="0:0:2" Storyboard.TargetProperty="Background.Color" >
<SplineColorKeyFrame Value="#A933FF"/>
<SplineColorKeyFrame Value="White" KeyTime="0:0:1"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<StopStoryboard BeginStoryboardName="beginAnimate"/>
<BeginStoryboard x:Name="beginAnimate" Storyboard="{StaticResource animate}"/>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I would like to make WPF Window that contains DataGrid control and enables following scenario in C# WPF DataGrid: Data is loaded in DataGrid, application validates data in background (parallel async operations), when row is determined to be valid its bacground color becomes green, red otherwise. What is cleanest way to program this behaviour? Is there any built-in functionality in DataGrid and WPF to do this kind of validation?
EDIT:
For now I have manged to perform this by using RowStyle, but this makes application non responsive because validation takes time for each row, so I would like to make this async and parallel.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding BgColor}">
</Setter>
</Style>
</DataGrid.RowStyle>
EDIT2:
Here is progress:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BgColor}" Value="DarkRed">
<Setter Property="Background" Value="DarkRed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Code behind looks like this:
Func<List<bool>> func = () => data.AsParallel().Select(x => File.Exists(x.FullPath)).ToList();
List<bool> res = null;
IAsyncResult ar = func.BeginInvoke(new AsyncCallback(x=>
{
res = ((Func<List<bool>>)((AsyncResult)x).AsyncDelegate).EndInvoke(x);
for (int i = 0; i < res.Count; ++i)
if (!res[i])
data[i].BgColor = Brushes.DarkRed;
}), null);
Remaining problem is that row background color is refreshed only when row is redrawn (moved out of view and than into view again). Any clean and easy way to fix this?
EDIT3:
Finally everything works exactly as required, only thing missing in EDIT2 was to implement INotifyPropertyChanged in data source class.
Well the best approach would be using a DataTrigger in the style of the DataGridItems and provide a property (bool?) in the ViewModel which is bound to the DataTrigger. In the DataTrigger you could declare the visual for all three states Null, True, False
For additional information on DataTrigger, please have a look here.
Edit
Hmm, any chance to put the highlighting functionality in a DataTemplate? I implemented a highlighting for the selection state of an entity. And it works as expected.
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="true">
<!-- Expand -->
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="CommandPanel">
<DoubleAnimation Duration="0:0:0.200" Storyboard.TargetProperty="Opacity" To="1" />
<DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="Height"
To="{StaticResource TargetHeightCommandPanel}" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<!-- Collapse -->
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="CommandPanel">
<DoubleAnimation Duration="0:0:0.100" Storyboard.TargetProperty="Opacity" To="0" />
<DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="Height" To="0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
Btw, have you ever heard of MVVM?
is it possible to create a c# animation for the AttachedPropertys like Alignment? Maybe 1 Second Move between the Change from HorizontalAlignment.Left to HorizontalAlignment.Right - is it possible?
Thanks a lot.
You can't animate it in the sense of producing a smooth animation where something slides from left to right since they are discrete states. There aren't any in-between values. It is possible to create an "animation" which changes the alignment at some point from left to right, it just won't slide across. You could also do a lot of work and measure all the controls to manually create an animation which moves something from one side of the screen to the other using things like Canvas.Left or margins to position the controls.
The attached property part is not an issue, just use the full name of the attached property in the target property part of your animation.
While it's not possible to directly animate smoothly between two properties like
HorizontalAlignment="Right" VerticalAlignment="Bottom"
to
HorizontalAlignment="Center" VerticalAlignment="Center"
I did come up with a way to do this for an app in a way that I thought might be worth sharing. I simply placed the control in a grid that took up the full pane of the window. I aligned the control to the bottom right of the grid. Then I animated the grid with to transform and scale the corner down that I need to the point I want to align the control too (center in this example. See the complete code below.
<Grid Margin="5,5,14,70" Visibility="{Binding Path=AdminModeIsEnabled, Converter={StaticResource CollapsedVisibilityConverter}, FallbackValue=Visible}">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding CenterPanel}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To=".5" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To=".5" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Height="15" Source="\Images\Test.png" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
</Grid>
It is possible, here is an example:
class StoryBoardManager : System.Windows.Media.Animation.Storyboard
{
public void ChangeRectangleAlignment(DependencyObject target, VerticalAlignment verticalAlignment, HorizontalAlignment horizontalAlignment, int BeginTimeMillisecond)
{
ObjectAnimationUsingKeyFrames objectAnimation = new ObjectAnimationUsingKeyFrames()
{
BeginTime = TimeSpan.FromMilliseconds(0)
};
Storyboard.SetTarget(objectAnimation, target);
Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("(FrameworkElement.HorizontalAlignment)"));
DiscreteObjectKeyFrame keyFrame = new DiscreteObjectKeyFrame(horizontalAlignment, TimeSpan.FromMilliseconds(BeginTimeMillisecond));
objectAnimation.KeyFrames.Add(keyFrame);
this.Children.Add(objectAnimation);
}
}
For more information, see this other question.