I am having trouble getting my tabitem to flash when the value for newCall is true. I think i have the Xaml correct but i am not sure how to bind it behind in code. When the variable new Call is set to true i would like my tabitem to flash.
<TabItem.Style>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource MetroTabItem}">
<Style.Resources>
<Storyboard x:Key="flashAnimation" >
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" AutoReverse="True" Duration="0:0:0.5" RepeatBehavior="Forever" />
</Storyboard>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding newCall}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="flash" Storyboard="{StaticResource flashAnimation}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
Your ViewModel has to implement INotifyPropertyChanged. In the ViewModel add the following Code:
private bool _newCall;
public bool newCall
{
get { return _newCall; }
set
{
_newCall = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("newCall"));
}
}
}
and change Binding="{Binding newCall} to Binding="{Binding newCall, UpdateSourceTrigger=PropertyChanged}"
Then, the TabItem will start flashing as soon as newCall is set to true
Related
I tried to execute animation with DataTrigger but it don't reise up.
Program don't throw any exception and errors.
Can someone explain why animation don't Start ?
Here is my storyboard
<Storyboard x:Key="storyboard2" AutoReverse="True" RepeatBehavior="Forever" Duration="00:01:00">
<DoubleAnimation Storyboard.TargetProperty="(Rectangle.RenderTransform).(ScaleTransform.ScaleX)" Storyboard.Target="{Binding Source={x:Reference RectangleScaleFrom}}" To="4" From="1"/>
<DoubleAnimation Storyboard.TargetProperty="(Rectangle.RenderTransform).(ScaleTransform.ScaleY)" Storyboard.Target="{Binding Source={x:Reference RectangleScaleFrom}}"
From="4" To="1"/>
</Storyboard>"
Here Is DataTrigger to start and Stop Animation
<DataTrigger Binding="{Binding RunAnimation}" Value="True" x:Key="Start">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="BeginScaleFrom" Storyboard="{StaticResource storyboard2}"/>
<!--<BeginStoryboard x:Name="BeginScaleTo" Storyboard="{StaticResource storyboard2}"/>-->
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding RunAnimation}" Value="False" x:Key="Stop">
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginScaleFrom"/>
<!--<StopStoryboard BeginStoryboardName="BeginScaleTo"/>-->
</DataTrigger.ExitActions>
</DataTrigger>
Here is Button
<ToggleButton Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="50,20,50,30" FontSize="20" x:Name="StartButton" Style="{StaticResource StartStopButtonStyle}" Width="100" Height="50" Command="{Binding StartAnimation}">
</ToggleButton>
And Command to start
private bool _StartAnim = false;
public bool RunAnimation
{
get => _StartAnim;
set
{
_StartAnim = value;
RaisePropertyChanged(nameof(RunAnimation));
}
}
public RelayCommand StartAnimation { get; private set; }
public MainViewModel()
{
StartAnimation = new RelayCommand(() => RunAnimation = !RunAnimation);
}
Something like this should work:
<Window.Resources>
<Storyboard x:Key="storyboard2">
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX"
To="4" From="1" AutoReverse="True"
RepeatBehavior="Forever" Duration="00:00:10"/>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY"
From="4" To="1" AutoReverse="True"
RepeatBehavior="Forever" Duration="00:00:10"/>
</Storyboard>
</Window.Resources>
<Rectangle Width="100" Height="100" Fill="Red">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RunAnimation}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="BeginScaleFrom"
Storyboard="{StaticResource storyboard2}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginScaleFrom"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
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!
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>
I have created a fade in and out animation, triggered by property changes in the viewmodel. This is working fine when fading in and out the first time, but each time I repeat this only the fade out is displayed, meaning the control remains invisible until it flashes to opacity 1 and then fades out.
XAML:
<DataTemplate x:Key="MessageTemplate">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding FadeInAnimationState}" Value="Active">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding FadeOutAnimationState}" Value="Active">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
<StatusBarItem>
...
</StatusBarItem>
</DataTemplate>
...
<ContentPresenter ContentTemplate="{StaticResource MessageTemplate}" Content="{Binding}"/>
The Viewmodel properties responsible for trigggering the animation:
private DisplayState _messageState;
private DisplayState MessageState
{
get { return _messageState; }
set
{
_messageState = value;
if (value == DisplayState.Displayed)
{
FadeOutAnimationState = AnimationState.Inactive;
FadeInAnimationState = AnimationState.Active;
}
else
{
FadeInAnimationState = AnimationState.Inactive;
FadeOutAnimationState = AnimationState.Active;
}
}
}
public AnimationState FadeInAnimationState
{
... // getter and setter with NotfiyPropertyChanged
}
public AnimationState FadeOutAnimationState
{
... // getter and setter with NotfiyPropertyChanged
}
And the call (debug):
MessageState = DisplayState.Displayed;
await Task.Delay(duration);
MessageState = DisplayState.Hidden;
await Task.Delay(TimeSpan.FromSeconds(1));
What am I doing wong?
You need to remove your storyboards after you are done with them. Something like:
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="FadeIn" />
<RemoveStoryboard BeginStoryboardName="FadeOut" />
<BeginStoryboard Name="FadeIn">
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
(and same for FadeOut, give it a name)
Or, you can do it on the ExitActions:
<DataTrigger.EnterActions>
<BeginStoryboard Name="FadeIn">
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="FadeIn" />
</DataTrigger.ExitActions>
Otherwise the last storyboard will keep "pushing" its last value so you won't see any changes
I am stuck with an animation in WPF. I need to animate a row(blink) in the datagrid when in that row all checkboxs options are checked(total 3).
<DataGrid x:Name="employeesGrid" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" SelectedIndex="{Binding SelectedEmployeeIndex, Mode=TwoWay}" Style="{StaticResource dataGridStyle}" ItemsSource="{Binding Employees}">
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NumberOfSkills}" Value="3">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="blinkingRow">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.5" RepeatBehavior="Forever" AutoReverse="true" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard x:Name="notBlinkingRow">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="1" Duration="0:0:0.5" RepeatBehavior="Forever" AutoReverse="true" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
...
<DataGridTemplateColumn Header="Skills" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Skills, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=DataContext.SaveChangesCommand, RelativeSource={RelativeSource Mode=Self}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid>
In view model I have.
public int NumberOfSkills
{
get
{
int count = 0;
foreach(Skill skill in ((EmployeeViewModel)SelectedRow).Skills)
{
if(skill.IsChecked)
{
count++;
}
}
return count;
}
}
Why this isn't working? I tried a lot of solutions which I finded on the internet, but nothing isn't working. The posted example is very similiar with code on msdn site,so I really don't know what is problem here.
There are couple of problems you need to address to fix this issue.
get
{
int count = 0;
foreach(Skill skill in ((EmployeeViewModel)SelectedRow).Skills)
{
if(skill.IsChecked)
{
count++;
}
}
return count;
}
Inside a get of a property you should only return values, and should not change any value. It violates - [Command Query Separation principle][1]
Instead just return private variable _count
private int _count
public int NumberOfSkills
{
get
{
return _count;
}
set
{
_count = value;
NotifyPropertyChanged("NumberOfSkills")
}
}
Now the question comes where should you set the logic for NumberOfSkills? So you should keep in a place where the user/actor changes it.
Lets say you set IsChecked property is set from UI by the user of the class Skills. You can subscribe to IsChecked PropertyChanged() and separate the logic where you update the NumberOfSkills.
skill.PropertyChanged += SkillsPropertyChanged
SkillsPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName ="IsChecked")
{
//Logic to update the NumberOfSkills
if(skill.IsChecked)
{
NumberOfSkills++;
}
else
{
NumberOfSkills--;
}
}
}
The quick and Short Way to do this with your existing approach is simple. Just call NotifyPropertyChanged("NumberOfSkills") when you set the IsCheckedProperty. Although this might not be straight forward hope you get the idea.
i.e.
private bool _isChecked;
public bool IsChecked
{
get {return _isChecked;}
set
{
_isChecked = value;
NotifyPropertyChanged("IsChecked");
NotifyPropertyChanged("NumberOfSkills");
}
}
I have a Style with a Storyboard and Triggers. The animation work nicely, but only once.
I have 2 Storyboards FadeIn and FadeOut. In the EnterActions I start the FadeIn animation and in the ExitActions the FadeOut animation. I start the whole animation in code with
TextBlock.StartFade = true;
When i debug the above code, with every hit StartFade is False (which is correct).
So what do i do wrong?
Here is the Style in XAML. FadingTextBlock is just a custom TextBlock with a StartFade dependency property.
<Style TargetType="{x:Type Controls:FadingTextBlock}">
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="StartFade" Value="False" />
<Setter Property="Opacity" Value="1.0" />
<Style.Resources>
<Storyboard x:Key="FadeIn">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Controls:FadingTextBlock.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0:0:0.0" Value="0.0" />
<EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1.0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Controls:FadingTextBlock.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(Controls:FadingTextBlock.StartFade)" Storyboard.TargetName="{x:Null}">
<DiscreteBooleanKeyFrame KeyTime="0:0:1.5" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOut">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Controls:FadingTextBlock.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0:0:0.0" Value="1.0" />
<EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="0.0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Controls:FadingTextBlock.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:1.5" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<Trigger Property="StartFade" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="In" Storyboard="{StaticResource FadeIn}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard x:Name="Out" Storyboard="{StaticResource FadeOut}" />
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
You should stop the started storyboard(s) from playing in the exit action using the <StopStoryboard> action.
<Trigger Property="StartFade" Value="True">
<Trigger.EnterActions>
<StopStoryBoard BeginStoryboardName="Out"/>
<BeginStoryboard x:Name="In" Storyboard="{StaticResource FadeIn}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryBoard BeginStoryboardName="In"/>
<BeginStoryboard x:Name="Out" Storyboard="{StaticResource FadeOut}" />
</Trigger.ExitActions>
</Trigger>
I ended up using a local animation in code.
Set the TextBlock Opacity to 0 in Xaml.
// Fading animation for the textblock to show that the settings are updated.
DoubleAnimation fadingAnimation = new DoubleAnimation();
fadingAnimation.From = 0;
fadingAnimation.To = 1;
fadingAnimation.Duration = new Duration(TimeSpan.FromSeconds(1.5));
fadingAnimation.AutoReverse = true;
UpdateMessage.BeginAnimation(TextBlock.OpacityProperty, fadingAnimation);
I Implemented my own C# solution for this, based on #MystyxMac
Extracted it to a blend behavior, which exposed a challenge..
Attempting to show the same notification twice in a row will not work since the dependency property changed callback will not be called..
I overcame this by obtaining the binding, clearing it and setting it again.
public class ShowFadingTextBehavior : System.Windows.Interactivity.Behavior<TextBlock>
{
public static readonly DependencyProperty DurationProperty = DependencyProperty.Register(
"Duration", typeof(TimeSpan), typeof(ShowFadingTextBehavior), new PropertyMetadata(TimeSpan.FromSeconds(5)));
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof (string), typeof (ShowFadingTextBehavior), new PropertyMetadata("",OnTextChanged));
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = (ShowFadingTextBehavior) d;
var text = (string) e.NewValue;
if(string.IsNullOrEmpty(text))
return;
b.Show(text);
}
private void Show(string text)
{
var textBlock = AssociatedObject;
if(textBlock==null)
return;
textBlock.Text = text;
if(textBlock.Visibility==Visibility.Visible)
return;
textBlock.Visibility = Visibility.Visible;
var a = new DoubleAnimation
{
From = 1.0,
To = 0.0,
FillBehavior = FillBehavior.Stop,
BeginTime = TimeSpan.FromSeconds(1),
Duration = new Duration(Duration)
};
var storyboard = new Storyboard();
storyboard.Children.Add(a);
Storyboard.SetTarget(a, textBlock);
Storyboard.SetTargetProperty(a, new PropertyPath(UIElement.OpacityProperty));
storyboard.Completed += delegate
{
textBlock.Visibility = Visibility.Collapsed;
textBlock.Opacity = 1.0;
var binding = BindingOperations.GetBinding(this, TextProperty);
if(binding==null)
return;
ClearValue(TextProperty);
BindingOperations.SetBinding(this, TextProperty, binding);
};
storyboard.Begin();
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}