Style scope problem - wpf

I have a couple of TextBlocks, bound to different things. Both TextBlocks have the same style applied. In the style there is an eventtrigger which flashes the text when the bound value updates. All works great however when the value for one textblock updates, both textblocks flash. I was expecting just one TextBlock to flash. Any ideas?
<Style x:Key="flashingTextBlock" TargetType="TextBlock">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
To="Orange"
Duration="0:0:1"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
<TextBlock Text="{Binding Path=PcName, NotifyOnTargetUpdated=True}"
Style="{StaticResource flashingTextBlock}"/>
<TextBlock Text="{Binding Path=Time, NotifyOnTargetUpdated=True}"
Style="{StaticResource flashingTextBlock}"/>

Basically I cannot reproduce this (with a similar config).
I suggest you verify what is actually happening. It could be that your codebehind (ViewModel) is calling PropertyChanged to enthusiastically.

Related

WPF: Storyboard in style returning a "Cannot animate 'Color' on an immutable object instance."

I have the following XAML:
<UserControl.Resources>
<SolidColorBrush x:Key="Brush"></SolidColorBrush>
<Style TargetType="{x:Type StackPanel}" x:Key="ColourChangingStyle">
<Setter Property="Background" Value="{StaticResource Brush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path='Stage'}" Value="1">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.Target="{StaticResource Brush}"
Storyboard.TargetProperty="Color" From="{StaticResource FirstColor}" To="{StaticResource FinishedColor}" Duration="0:0:10" BeginTime="0:0:0"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel x:Name="InfoPanel" Orientation="Vertical" Margin="1,2" Style="{DynamicResource ColourChangingStyle}">
...
</StackPanel>
And I keep getting the same error that:
Cannot animate 'Color' on an immutable object instance
which is the brush. I looked up the problem, and believe that it's something to do with the binding the brush to the StackPanel making it unavailable to alter later.
Is there any way around this, I literally have no clue what my other options for the same effect are without hardcoding colors in, and doing all the events in code.
It seems that you can not animate a Brush in Resources with Storyboard.Target. But you can set the Background of your Style (you have done this already) and animate this property.
<ColorAnimation Storyboard.TargetProperty="Background.Color"
From="{StaticResource FirstColor}"
To="{StaticResource FinishedColor}"
Duration="0:0:10" BeginTime="0:0:0" />

Trouble using a DataTrigger with Rectangle in WPF

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.

WPF: Creating a ListView with expanding ListItems

So I want a list of items that when you select them they expand to show more info (no toggleButton).
I figure there are multiple ways to do this but what I started at was that I had a ListView bound to the collection of viewModels and then defined the ViewModel view as Expander. Problem here was binding the selected one to be expanded.
I started getting multiple ideas on how this could be done differently. Perhaps modding the ControlTemplate of the ListView to have it's items set as my own type of expander. But I'm not sure how well that works when the ItemsSource is set for the list.
Problem is I'm not too sure what the best way here is.
You can easily select the DataTemplate of the selected ListViewItem by setting ListView.ItemContainerStyle and using appropriate triggers.
Here's an example of how you can not only change the visual tree of the selected item, but also animate its properties at the same time as well.
<ListView ItemsSource="{Binding ...}">
<ListView.Resources>
<!-- this is what unselected items will look like -->
<DataTemplate x:Key="DefaultItemTemplate">
<TextBlock FontSize="12" Margin="0,0,10,0" Text="Unselected" />
</DataTemplate>
<DataTemplate x:Key="SelectedItemTemplate">
<Border BorderBrush="Red" BorderThickness="2" Padding="5">
<TextBlock FontSize="12" Margin="0,0,10,0" Text="Selected" />
</Border>
</DataTemplate>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<!-- set properties for all items -->
<Setter Property="Margin" Value="0,2,0,2" />
<Setter Property="Padding" Value="0,2,0,2" />
<Setter Property="ContentTemplate" Value="{StaticResource DefaultItemTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<!-- change what the selected item looks like -->
<Setter Property="ContentTemplate" Value="{StaticResource SelectedItemTemplate}" />
<!-- animate it as well -->
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="MinHeight" To="80" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="MinHeight" To="0" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>

How to animate ScaleY when Element becomes visible

I'd like to make a WPF UI Element appear to expand vertically when its Visibility property transitions to "Visible". I don't want to hard-code the Height in the animation since I'd like to apply this animation to any UI Element as a Style. So, I'm trying to use ScaleY but am not having any luck. Here is the XAML for the style and the listbox:
<Style x:Key="VerticalGrow" TargetType="ListBox">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="TransformGroup.ScaleTransform.ScaleY" BeginTime="0:0:0.5" From="0" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
<ListBox Grid.Row="2" MaxHeight="60" MinHeight="60" Visibility="{Binding MyViewModel.ListBoxVisibility}" IsSynchronizedWithCurrentItem="False" ItemsSource="{Binding MyViewModel.ListBoxItems}" Style="{DynamicResource VerticalGrow}" IsTabStop="True">
</ListBox>
I get an runtime exception complaining that:
"Cannot convert the value in attribute 'Style' to object of type 'System.Windows.Style'. Cannot resolve all property references in the property path 'TransformGroup.RenderTransform.ScaleTransform.ScaleY'. Verify that applicable objects support the properties. Error at object 'System.Windows.Controls.ListBox' in markup file 'MyApp;component/mainwindow.xaml' Line 69 Position 399."}
ListBox doesn't have a property TransformGroup. I think you want to set RenderTransform or LayoutTransform to a ScaleTransform, and then animate that.
<Style x:Key="VerticalGrow" TargetType="ListBox">
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.ScaleY"
BeginTime="0:0:0.5" From="0" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>

Get a Framework element from a Storyboard

I have a reference to an instance of a Storyboard object, and want to get hold of the Framework element that it is attached to / animating. I haven't been able to come up with any way of doing this.
For example, in the XAML below, can I go from a reference to the Storyboard to get hold of either the Label or the Grid
<Grid>
<Grid.Resources>
<Storyboard x:Key="myStoryboard">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:5"/>
</Storyboard>
<Style x:Key="myStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=StartAnimation}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource myStoryboard}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Label x:Name="labelHello" Grid.Row="0" Style="{StaticResource myStyle}">Hello</Label>
</Grid>
For those wondering why on earth I need to do this, it is because I am trying to create a derived Storyboard class or an attached behaviour that will allow me to specify a method name on the DataContext to be called when the Storyboard completed event fires. This will allow me to do pure MVVM rather than needing some code behind to call into my View Model.
If you changed your XAML to something like this:
<Grid x:Name="grid">
<Grid.Resources>
<Storyboard x:Key="myStoryboard">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:5" Storyboard.Target="{Binding ElementName = grid}"/>
</Storyboard>
<Style x:Key="myStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=StartAnimation}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource myStoryboard}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Label x:Name="labelHello" Grid.Row="0" Style="{StaticResource myStyle}">Hello</Label>
</Grid>
This introduces an x:Name to the grid and a Storyboard.Target to the DoubleAnimation. You can now get a reference to the grid with this code:
Storyboard sb = //You mentioned you had a reference to this.
var timeLine = sb.Children.First();
var myGrid = Storyboard.GetTarget(timeLine);

Resources