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); }
}
}
Related
I have a grid that changes its background gradient by tag property. When I set tag to "Enabled" the animation works fine but the problem is when I set tag to "Disabled" the animation does not start. From "Enabled" I can only display animation for tag "Prohibited" and then I can't go back to "Disabled" or "Enabled".
Am I missing something or doing something wrong?
Thanks in advance
<Grid.Style>
<Style TargetType = "Grid">
<Style.Triggers>
<Trigger Property = "Tag" Value = "Disabled">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[0].Color"
From = "{x:Null}"
To = "{StaticResource disabled_1}" Duration = "0:0:1" />
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[1].Color"
From = "{x:Null}"
To = "{StaticResource disabled_2}" Duration = "0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property = "Tag" Value = "Enabled">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[0].Color"
From = "{x:Null}"
To = "{StaticResource enabled_1}" Duration = "0:0:1" />
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[1].Color"
From = "{x:Null}"
To = "{StaticResource enabled_2}" Duration = "0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property = "Tag" Value = "Prohibited">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[0].Color"
From = "{x:Null}"
To = "{StaticResource prohibited_1}" Duration = "0:0:1" />
<ColorAnimation
Storyboard.TargetProperty = "Background.GradientStops[1].Color"
From = "{x:Null}"
To = "{StaticResource prohibited_2}" Duration = "0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
You have defined the animations, but you also need to define how to stop the animations in order for other animations to start.
There are two ways to do this.
Define ExitActions on all your Triggers.
Example:
<Trigger Property = "Tag" Value = "Enabled">
<Trigger.EnterActions>
<BeginStoryboard Name="enabledsb">
// Removed for brevity
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="enabledsb"/>
</Trigger.ExitActions>
</Trigger>
Define a StopStoryboard on your animations, before you animate something new.
Example:
<Trigger Property = "Tag" Value = "Disabled">
<Trigger.EnterActions>
<StopStoryboard BeginStoryboardName="enabledsb"/>
<BeginStoryboard Name="disabledsb">
// Removed for brevity
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
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 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
I have a C# WPF Page and on it I have placed several small images that I want to act like check boxes (I have my own custom images for hover and selected states).
I am manually changing the images like so:
<Image x:Name="Image_Custom" Source="/Images/checkcircle_off.png" Width="16" Height="16" HorizontalAlignment="Left" Margin="30,107,0,0" VerticalAlignment="Top" MouseEnter="Image_Custom_MouseEnter" MouseLeave="Image_Custom_MouseLeave" MouseUp="Image_Custom_MouseUp" MouseLeftButtonDown="Image_Custom_MouseLeftButtonDown"/>
private void Image_Custom_MouseEnter(object sender, MouseEventArgs e)
{
if (_selected == false)
{
var uriSource = new Uri("/Images/checkcircle_hover.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
}
}
private void Image_Custom_MouseLeave(object sender, MouseEventArgs e)
{
if (_selected == false)
{
var uriSource = new Uri("/Images/checkcircle_off.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
}
}
private void Image_Custom_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_selected)
{
var uriSource = new Uri("/Images/checkcircle_off.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
_selected = false;
}
else
{
var uriSource = new Uri("/Images/checkcircle_on.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
_selected = true;
}
}
private void Image_Custom_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_selected)
{
var uriSource = new Uri("/Images/checkcircle_off.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
_selected = false;
}
else
{
var uriSource = new Uri("/Images/checkcircle_on.png", UriKind.Relative);
Image_Custom.Source = new BitmapImage(uriSource);
_selected = true;
}
}
This works but is very cumbersome and I will have up to 20 check boxes.
How can I create a custom XAML Style that I can use for each image or something similar.
EDIT:
I have used the following style to handle the hover over:
<Page.Resources>
<Style TargetType="Image" x:Key="checkBoxStyle">
<Setter Property="Source" Value="/Images/checkcircle_off.png"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="/Images/checkcircle_hover.png"/>
</Trigger>
</Style.Triggers>
</Style>
</Page.Resources>
<Image x:Name="Image_Custom" Style="{StaticResource checkBoxStyle}" Width="16" Height="16" HorizontalAlignment="Left" Margin="30,107,0,0" VerticalAlignment="Top" MouseEnter="Image_Custom_MouseEnter" MouseLeave="Image_Custom_MouseLeave" MouseUp="Image_Custom_MouseUp" MouseLeftButtonDown="Image_Custom_MouseLeftButtonDown"/>
But I dont know how to handle the clicked event. How can I do this?
EDIT 2
I have did the following:
<Style TargetType="{x:Type CheckBox}" x:Key="myCheckBoxStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Image x:Name="checkBoxImage" Source="/Images/checkcircle_off.png"></Image>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="checkBoxImage" Property="Source" Value="/Images/checkcircle_on.png"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="checkBoxImage" Property="Source" Value="/Images/checkcircle_off.png"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="checkBoxImage" Property="Source" Value="/Images/checkcircle_hover.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<CheckBox Content="My CheckBox" Style="{StaticResource myCheckBoxStyle}" Width="16" Height="16" Foreground="white" FontSize="16" HorizontalAlignment="Left" Margin="30,242,0,0" VerticalAlignment="Top" />
The correct images appear when hovered, checked and unchecked.
But I noticed that the Content has disappeared ("My Checkbox") and also I only want the hover state to appear when its not checked, how can I do that?
In WPF you generally look for a control that has the functionality you need and then you make it look like you want. So if you want CheckBox functionality then you use CheckBox control and change its Template to be what you want. So you can create Style for CheckBox that will set your custom Template
<Window.Resources>
<Style TargetType="{x:Type CheckBox}" x:Key="myCheckboxStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Image x:Name="checkboxImage" Source="normal.png" Width="32"/>
<ContentPresenter/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="checkboxImage" Property="Source" Value="checked.png"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="False"/>
</MultiTrigger.Conditions>
<Setter TargetName="checkboxImage" Property="Source" Value="hover.png"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
and then just use it on any CheckBox
<CheckBox Style="{StaticResource myCheckboxStyle}" Content="ABC"/>
and you'll have all CheckBox functionality with your custom looks
If you override CheckBox and create a specific style this will look like this:
public class MyCheckBox : CheckBox
{
#region ImageNormal
/// <summary>
/// ImageNormal Dependency Property
/// </summary>
public static readonly DependencyProperty ImageNormalProperty =
DependencyProperty.Register("ImageNormal", typeof(ImageSource), typeof(MyCheckBox),
new FrameworkPropertyMetadata((ImageSource)null));
/// <summary>
/// Gets or sets the ImageNormal property. This dependency property
/// indicates ....
/// </summary>
public ImageSource ImageNormal
{
get { return (ImageSource)GetValue(ImageNormalProperty); }
set { SetValue(ImageNormalProperty, value); }
}
#endregion
#region ImageChecked
/// <summary>
/// ImageChecked Dependency Property
/// </summary>
public static readonly DependencyProperty ImageCheckedProperty =
DependencyProperty.Register("ImageChecked", typeof(ImageSource), typeof(MyCheckBox),
new FrameworkPropertyMetadata((ImageSource)null));
/// <summary>
/// Gets or sets the ImageChecked property. This dependency property
/// indicates ....
/// </summary>
public ImageSource ImageChecked
{
get { return (ImageSource)GetValue(ImageCheckedProperty); }
set { SetValue(ImageCheckedProperty, value); }
}
#endregion
//... other image properties removed for simplicity
static MyCheckBox()
{
//Override base class style
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCheckBox), new FrameworkPropertyMetadata(typeof(MyCheckBox)));
}
}
Associated XAML Style:
<Style TargetType="{x:Type local:MyCheckBox}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="grdNormal">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="grdMouseOver">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="grdPressed">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="grdNormal">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgUnchecked1">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgChecked1">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgUnchecked2">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgChecked2">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgUnchecked3">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="imgChecked3">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="grdNormal">
<Image x:Name="imgUnchecked1" Source="{Binding ImageNormal, RelativeSource={RelativeSource TemplatedParent}}"/>
<Image x:Name="imgChecked1" Visibility="Collapsed" Source="{Binding ImageNormal, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
<Grid x:Name="grdMouseOver" Visibility="Collapsed">
<Image x:Name="imgUnchecked2" Source="{Binding ImageMouseOver, RelativeSource={RelativeSource TemplatedParent}}"/>
<Image x:Name="imgChecked2" Visibility="Collapsed" Source="{Binding ImageMouseOverChecked, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
<Grid x:Name="grdPressed" Visibility="Collapsed">
<Image x:Name="imgUnchecked3" Source="{Binding ImagePressed, RelativeSource={RelativeSource TemplatedParent}}"/>
<Image x:Name="imgChecked3" Visibility="Collapsed" Source="{Binding ImagePressedChecked, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>