I have a button which shows a text and an image:
For this I use a StackPanel with a TextBlock and an Image inside of it.
When the variable "ActiveState" changes the Background of the StackPanel should change too, for this I use DataTriggers
ActiveState=0 -> Red /
ActiveState=1 -> Blue /
ActiveState=2 -> blinking Blue (for this I use a Storyboard and a Color Animation)
The blinking Trigger (Value=2) is working fine, but the two other Triggers (Value=0 + Value=1) are not working.
When I remove the Background of the Stackpanel (Background="Transparent") the first two Triggers are working but the last one get the following Exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
Additional information: Background property does not point to a dependencyobject in path '(0).(1)'
This is my code:
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<StackPanel Orientation="Horizontal" Name="SelectButtonStackpanel" Background="Transparent">
<TextBlock Text="{Binding Text}"/>
<Image Source="{Binding Image}" Stretch="Uniform" Height="40" Width="40"/>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveState}" Value="0">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveState}" Value="1">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveState}" Value="2">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(StackPanel.Background).(SolidColorBrush.Color)"
To="Blue" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(StackPanel.Background).(SolidColorBrush.Color)"
Duration="0:0:1"
>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
Do you have any idea how I get all three Triggers working?
best regards
Phil
When you directly set Background="Transparent" on the StackPanel, this has higher precedence than a value set from a Style Setter. So remove the direct assignment and add another Setter for the default Background.
Besides that, if you want to animate Background.Color you should always explictly assign SolidColorBrushes instead of predefined Brushes like Background="Red".
<StackPanel Orientation="Horizontal" Name="SelectButtonStackpanel">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveState}" Value="0">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Red"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveState}" Value="1">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Blue"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveState}" Value="2">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Background.Color"
To="Blue" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="Forever">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
Related
I have a CheckBox (placed above an image) whose Opacity is set to 0 by default. When IsMouseOver is true, the CheckBox Opacity property is animated from 0 to 1 and reversed when IsMouseOver is false. However, if the CheckBox IsSelected is true, then the reverse animation(i.e DataTrigger.ExitActions) should not execute. i.e CheckBox should remain visible if it is checked. In my XAML the checkbox is disappearing even when it is checked.
Following is my XAML:
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Opacity" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=imageGrid, Path=IsMouseOver}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Timeline.DesiredFrameRate="100">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="00:00:00.400" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard Timeline.DesiredFrameRate="100">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="00:00:00.400" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Mode=Self}}" Value="true">
<Setter Property="Opacity" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
Implemented the way #feO2x suggested. However it is not working as expected. Got it work in a simple application. However I intend to use the checkbox in a ListViewItem. Following is my XAML:
<Style x:Key="ThumbView_ItemContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="175" />
<Setter Property="Width" Value="125" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid Background="Pink" x:Name="mygrid">
<Rectangle Name="LBRect" Fill="Transparent" Opacity="0.195" />
<Image Name="posterImg" Margin="0,0,0,0" RenderOptions.BitmapScalingMode="HighQuality" SnapsToDevicePixels="True" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding Path=ProfilePic}" Height="125" MaxWidth="125" />
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="CheckBox">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.1" ScaleY="1.1" />
</CheckBox.RenderTransform>
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource ThumbViewCheckBoxStyle}">
<Setter Property="Opacity" Value="0" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" Value="False" />
<Condition Binding="{Binding ElementName=CheckBox, Path=IsChecked}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.0" Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1.0" Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This can be achieved with IMultiValueConverter in place. Pass two bindings to it i.e. IsMouseOver and IsChecked.
Converter:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
bool isMouseOver = (bool)values[0];
bool isChecked = (bool)values[1];
return isChecked || isMouseOver;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="IsMouseOver" ElementName="imageGrid"/>
<Binding Path="IsChecked" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</DataTrigger.Binding>
<!-- Rest same trigger -->
</DataTrigger>
Of course you need to add MyValueConverter instance as resource in your XAML file.
Update February 26th: In your Style that is not working, you reference the checkbox the wrong way. ElementName can only be used in a binding if the target control was given a name and is accessible within the current WPF XAML namescope (you can learn about XAML namescopes here).
You should use a relative source within your binding to reference the CheckBox correctly: RelativeSource={RelativeSource Self}. The following ListView style shows the whole example:
<Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
<CheckBox IsChecked="{Binding IsSelected}" Grid.Column="1">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Opacity" Value="0.0" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="False" />
<Condition Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}"
Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="0.0" Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="1.0" Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can download a working example from here (DropBox link).
Original Answer:
You can use a MultiDataTrigger within your style to accomplish the behavior you want. Have a look at the following code:
<CheckBox Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Check Me" Name="CheckBox">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Opacity" Value="0.0" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Rectangle, Path=IsMouseOver}" Value="False" />
<Condition Binding="{Binding ElementName=CheckBox, Path=IsChecked}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="0.0"
Duration="0:0:0.4"
BeginTime="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="1.0"
Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
A MultiDataTrigger is used to describe two conditions that have to be true for the enter actions to be executed. In this case I choose that the IsMouseOver property of a rectangle as well as the IsChecked property of the checkbox must both be false. Thus the checkbox is faded in when the mouse is over the rectangle and stays visible when it becomes checked.
You can download my full sample here (it's a Dropbox link).
Hope this helps you. If you have any questions, please feel free to leave a comment.
I'm setting DataGridRow BackgroundColor to Green or to Red with trigger based on LogError value.
I want to animate newly added rows with transparency.
This works fine:
From="Transparent" To="Red"
But what I want color to goes to is current color set with Style. Its not always Red it could be Green as well.
This does not work:
From="Transparent" To="{TemplateBinding DataGridRow.Background}"
or
From="Transparent" To="{Binding RelativeSource={RelativeSource Self}, Path=Backgound}"
Code:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property = "Background" Value="LimeGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LogMessage}" Value="Exception has occured">
<Setter Property = "Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)"
Duration="00:00:03"
From="Transparent"
To="{TemplateBinding DataGridRow.Background}"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Error message: Cannot freeze this Storyboard timeline tree for use across threads.
There are few problems in your XAML code.
First, you have specified default style under resources section of DataGrid and later provide your own style which will override default style.
Either you should define new style and set its BasedOn DP to refer to default style. But in your case i don't see any use of defining separate style just for trigger.
Second, you want your animation to go from Transparent to colour selected in style which can either by LimeGreen or Red depending on trigger. So, you should not set To value in your animation since it will automatically picked up.
This will work as you desire -
<DataGrid>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property ="Background" Value="LimeGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LogMessage}"
Value="Exception has occured">
<Setter Property = "Background" Value="Red"/>
</DataTrigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty=
"(DataGridRow.Background).(SolidColorBrush.Color)"
Duration="00:00:03"
From="Transparent"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
#Den
You have to determinate a standart Color, before you animate. This is my Style for animating a row and cell.
<Style TargetType="{x:Type DataGridRow}">
<Style.Setters>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="Transparent"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#FFF37C21"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)" Duration="00:00:0.2" To="#FFF37C21"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)" Duration="00:00:0.2" To="Transparent"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Style.Setters>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="IsTabStop" Value="False" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid x:Name="gridCell" Background="#00FFFFFF">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Black"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextElement.FontWeight)">
<DiscreteObjectKeyFrame KeyTime="00:00:0.2">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Bold</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextElement.FontWeight)">
<DiscreteObjectKeyFrame KeyTime="00:00:0.2">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Light</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
I would like to have a button that blinks/animate when triggered by DataTrigger. I want to animate the button's background. Below is my xaml code.
<Window.Resources>
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Notification}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartBlinking">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="Orange" Duration="00:00:00.4" RepeatBehavior="Forever" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Notification}" Value="False">
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="StartBlinking"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid>
<Button x:Name="Button" Content="Button" Width="25" Height="25" Margin="158,62,320,224" Click="Button_Click"></Button>
<Button Style="{StaticResource ButtonStyle}" Content="Button" Focusable="False" Height="75" HorizontalAlignment="Left" Margin="23,146,0,0" Name="btnImgBrush" VerticalAlignment="Top" Width="160"></Button>
</Grid>
</Grid>
Here are the back end code:
public Boolean Notification
{
get { return new_notification; }
set
{
new_notification = value;
RaisePropertyChanged("Notification");
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Notification)
{
Notification = false;
}
else
{
Notification = true;
}
}
But, it didn't work. Any ideas why it didn't work?
Any help is greatly appreciated, Thanks.
At last its working. Thanks :)
<Window.Resources>
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Notification}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartBlinking">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="Orange" Duration="00:00:00.4" RepeatBehavior="Forever" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Notification}" Value="False">
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="StartBlinking"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Try this-
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Notification,RelativeSource={RelativeSource AncestorType=Window}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartBlinking">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" From="Transparent" To="Orange" Duration="00:00:00.4" RepeatBehavior="Forever" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Notification,,RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="StartBlinking"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Both TM Rocket and Vishal answer are correct but probably the following is the most correct and clean way to do it
<Window.Resources>
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Transparent"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Notification}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartBlinking">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="Orange" Duration="00:00:00.4" RepeatBehavior="Forever" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="StartBlinking"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
I'm new to WPF and am trying to animate the changes to a Rectangle's size and position. I have my rectangle being set by binding it to an INotifyPropertyChanged property. This works fine but the changes can be extreme and I'd like it to be visually less jarring.
This is working fine:
<Rectangle Fill="{x:Static SystemColors.ControlBrush}"
Stroke="black">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Canvas.Left" Value="{Binding Path=Coil.Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Coil.Top, Mode=OneWay}"/>
<Setter Property="Width" Value="{Binding Path=Coil.Width, Mode=OneWay}"/>
<Setter Property="Height" Value="{Binding Path=Coil.Height, Mode=OneWay}"/>
</Style>
</Rectangle.Style>
Coil.Left is a RectangleF structure within my INotifyPropertyChanged object and the DataContext property of my canvas containing the rectangle is being set in my code behind after the object is created.
I've also been able to animate this using an EventTrigger just fine:
<Rectangle Fill="{x:Static SystemColors.ControlBrush}"
Stroke="black">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Canvas.Left" Value="{Binding Path=Coil.Left, Mode=OneTime}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Coil.Top, Mode=OneTime}"/>
<Setter Property="Width" Value="{Binding Path=Coil.Width, Mode=OneTime}"/>
<Setter Property="Height" Value="{Binding Path=Coil.Height, Mode=OneTime}"/>
</Style>
</Rectangle.Style>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="{Binding Path=Coil.Width, Mode=OneWay}"
Duration="0:0:5"/>
<DoubleAnimation Storyboard.TargetProperty="Height"
To="{Binding Path=Coil.Height, Mode=OneWay}"
Duration="0:0:0.5"/>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
To="{Binding Path=Coil.Left, Mode=OneWay}"
Duration="0:0:5"/>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)"
To="{Binding Path=Coil.Top, Mode=OneWay}"
Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
When I move my mouse over the Rectangle, it changes via animation to the current property values of Coil.Left, Top, Width, and Height as expected.
However, what I want to accomplish is for any change to the Coil property values to be an animated change. So my plan was to set a DataTrigger and use a converter to return always true so that any change would result in the changes being played out via animation.
But regardless of what I do, I'm getting an exception error before it even opens the window when I use a DataTrigger and I've got no idea why this is happening. To keep it simple, I took out the converter and just used a straight value:
<Rectangle Fill="{x:Static SystemColors.ControlBrush}"
Stroke="black">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Canvas.Left" Value="{Binding Path=Coil.Left, Mode=OneTime}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Coil.Top, Mode=OneTime}"/>
<Setter Property="Width" Value="{Binding Path=Coil.Width, Mode=OneTime}"/>
<Setter Property="Height" Value="{Binding Path=Coil.Height, Mode=OneTime}"/>
</Style>
</Rectangle.Style>
<Rectangle.Triggers>
<DataTrigger Binding="{Binding Path=Coil.Height}" Value="60">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="{Binding Path=Coil.Width, Mode=OneWay}"
Duration="0:0:5"/>
<DoubleAnimation Storyboard.TargetProperty="Height"
To="{Binding Path=Coil.Height, Mode=OneWay}"
Duration="0:0:0.5"/>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
To="{Binding Path=Coil.Left, Mode=OneWay}"
Duration="0:0:5"/>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)"
To="{Binding Path=Coil.Top, Mode=OneWay}"
Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Rectangle.Triggers>
Any thoughts on why this would give me an exception error? I'm sure it's just a matter of my inexperience with WPF. This is my first project.
Thank you in advance.
--John
For a FrameworkElement like a Rectangle you can only use EventTrigger. In Style, ControlTemplate and DataTemplate you can use Trigger / MultiTrigger and DataTrigger / MultiDataTrigger too.
This means move your DataTrigger into Rectangle style and it should work:
<Rectangle Fill="{x:Static SystemColors.ControlBrush}" Stroke="Black">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Coil.Height}" Value="60">
...
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Was reading about this recently. Despite the similarity of EventTriggers and DataTriggers, you can use Binding inside the Storyboard for the EventTrigger but not the DataTrigger.
So you'll get a System.Windows.Markup.XamlParseException error if you try using binding inside the Storyboard condition such as below,
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsBarVisible,
Converter={StaticResource myBooleanToVisibiltyConverter}}" Value="Visible">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Value"
From="{Binding Path=ProgressedAmout}"
To="100"
Duration="0:0:50"
></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
You can use and EventTrigger in return or get rid of the Storyborad's To to From binding.
I am wondering if it possible to delay a datatrigger to change layout for 0.5 a second. Is any easy way to do it? I need to set the visibility of the object but wait for 0.5 a second. Any adeas are highly appreciated.
<DataTemplate x:Key="ListBoxItemDataTemplate">
<Grid x:Name="DataItem">
<Image x:Name="IconImage" Source="{Binding XPath=#icon}" Height="16" Margin="16,0,0,0" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock x:Name="ListboxIemtextBlock" Text="{Binding XPath=#name}" />
<Image x:Name="ArrowImage" Height="10" Source="Resources/Images/arrow_collapsed_grey.png" Visibility="{Binding XPath=#state}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True">
<Setter TargetName="ListboxIemtextBlock" Property="Foreground" Value="White"/>
<Setter TargetName="IconImage" Property="Source" Value="{Binding XPath=#iconSelected}"/>
<Setter TargetName="IconImage" Property="Height" Value="16"/>
<Setter TargetName="ArrowImage" Property="Source" Value="Resources/Images/arrow_collapsed_white.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True">
<Setter TargetName="ListboxIemtextBlock" Property="Foreground" Value="#FF6dacbe"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItem.Attributes[retract].Value}" Value="True">
<Setter TargetName="ListboxIemtextBlock" Property="Visibility" Value="Hidden" />
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartAnimation" Storyboard="{StaticResource MakeObjectVisibleAfterHalfASecond}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="StartAnimation"/>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers></DataTemplate>
Storyaboard:
<Storyboard x:Key="MakeObjectVisibleAfterHalfASecond" Storyboard.TargetName="ListboxIemtextBlock">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Duration="0" BeginTime="0:0:.5">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
It can be done using an animation. The pieces involved are:
1) An ObjectAnimationUsingKeyFrames that sets the Visibility property on the target, with a BeginTime of 0:0:.5 to delay this for a half second when the storyboard begins.
2) A DataTrigger that checks the property whose change will make the object visible (in this case, the IsChecked property on the CheckBox named Start).
3) BeginStoryboard in DataTrigger.EnterActions that launches the animation, and a RemoveStoryboard in DataTrigger.ExitActions that makes the object invisible again if the bound property changes back.
Here's a simple working example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Storyboard x:Key="MakeObjectVisibleAfterHalfASecond">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
Duration="0"
BeginTime="0:0:.5">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<DockPanel>
<CheckBox DockPanel.Dock="Top"
Margin="10"
x:Name="Start">Check this to make the label appear</CheckBox>
<Border BorderThickness="2"
BorderBrush="AliceBlue"
CornerRadius="5"
Margin="10"
Padding="10"
DockPanel.Dock="Top">
<Label Visibility="Hidden">
<Label.Content>This should appear a half second after the box is checked.</Label.Content>
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Start, Path=IsChecked}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="StartAnimation"
Storyboard="{StaticResource MakeObjectVisibleAfterHalfASecond}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="StartAnimation"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Border>
<TextBlock/>
</DockPanel>
</Page>
Note that you could also do this by omitting BeginTime and setting Duration on the animation, since the two are essentially the same thing with a key-frame animation.