Use style trigger to set property of a nested object - wpf

I have a small polygon written on the large canvas. I want to highlight a polygon when mouse is moving over the canvas. The code is like this:
<UserControl ...>
<Canvas Name="canvas" Height="22" Width="22">
<Canvas.Resources>
<Style TargetType="Canvas">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="false">
<Setter Property="polygon.Stroke" Value="#EEEEEE"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="polygon.Stroke" Value="Aqua"/>
</Trigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
<Polygon Points="11,1 16,6 16,16 11,21" Name="polygon">
<Polygon.Fill>
<SolidColorBrush Color="#EEEEEE"/>
</Polygon.Fill>
</Polygon>
</Canvas>
</UserControl>
However setter does not see the "polygon".

You cannot use Setters like that, if you use this kind of notation the engine will look for an attached property, or if no Style.TargetType was set for a property on the type before the dot.
The easiest thing to do is probably applying a style to the polygon itself and using a DataTrigger which binds to the Canvas so you can trigger on its properties.
<Polygon Points="11,1 16,6 16,16 11,21" Name="polygon">
<Polygon.Fill>
<SolidColorBrush Color="#EEEEEE"/>
</Polygon.Fill>
<Polygon.Style>
<Style TargetType="{x:Type Polygon}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource
AncestorType={x:Type Canvas}}}"
Value="True">
<Setter Property="Stroke" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Polygon.Style>
</Polygon>

Try EventTrigger, because other kinds of triggers you could only use in templates or styles. And we already know that Style.Trigger doesn't allow your scenario. So here is working example for you:
<Canvas Name="canvas" Height="22" Width="22">
<Polygon Points="11,1 16,6 16,16 11,21" Name="polygon">
<Polygon.Fill>
<SolidColorBrush x:Name="brush" Color="#EEEEEE"/>
</Polygon.Fill>
<Polygon.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<ColorAnimation From="#EEEEEE" To="Aqua" Duration="00:00:00.01" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
<ColorAnimation From="Aqua" To="#EEEEEE" Duration="00:00:00.01" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Polygon.Triggers>
</Polygon>
</Canvas>

It's looking for a property of the Canvas called 'polygon', which in turn has a property called 'Stroke'. You need to use TargetName if you want the setter to target a different object.
<Setter TargetName="polygon" Property="Stroke" Value="#EEEEEE" />

Related

Not all DataTriggers of StackPanel are working

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>

How to make Rotate in WPF

I beginner in wpf and I want rotate a TextBlock but I have error:"Cannot resolve all property references in the property path 'RotateTransform.Angle'. Verify that applicable objects support the properties."
<TextBlock Grid.Row="1" Grid.Column="1" RenderTransformOrigin="0.5,0.5" Style="{StaticResource Rotate}">
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-16.308"/>
</TransformGroup>
</TextBlock.RenderTransform>
<TextBlock.Background>
<ImageBrush ImageSource="image/1.png"></ImageBrush>
</TextBlock.Background>
</TextBlock>
and this is my style
<Style x:Key="Rotate" TargetType="{x:Type TextBlock}">
<!--<Setter Property="Width" Value="10"></Setter>
<Setter Property="Height" Value="10"></Setter>-->
<Setter Property="Background" Value="Black"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions >
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RotateTransform.Angle" To="-360" Duration="0:0:1" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
You should be able to use the orientation of a stack panel in your xaml to rotate easily, but this does not give immediate access to the angle.
<StackPanel Orientation="Horizontal">
<Textbox ...../>
</StackPanel>
Or in your style you can add a setter property.
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="-90"></RotateTransform>
</Setter.Value>
</Setter>
If you need TransformGroup in your RenderTransorm, then StoryboardTargetProperty will be look like
RenderTransform.Children[0].Angle
If you leave only RotateTransform there, then it will be
RenderTransform.Angle
In both cases, as you see, we start searching property from RenderTransform.

How to address children of a style ControlTemplate for animation?

I have a custom control extending a toggle button. This custom control has three dependency properties for ImageSources that can be used to customize Images that present a certain visual impression:
<Style TargetType="{x:Type local:SimpleFeedbackToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SimpleFeedbackToggleButton}">
<Grid x:Name="BtnGrid">
<Image x:Name="FeedbackImage" Source="{TemplateBinding FeedbackImageSource}" Visibility="Hidden" />
<Image x:Name="NormalImage" Margin="{TemplateBinding Padding}" Source="{TemplateBinding NormalImageSource}" />
<Image x:Name="DisabledImage" Source="{TemplateBinding DisabledImageSource}" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Visible" TargetName="DisabledImage"/>
<Setter Property="Opacity" Value="1.0" TargetName="BtnGrid"/>
</Trigger>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Visibility" Value="Visible" TargetName="FeedbackImage"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding FeedbackBlink, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="SB_BlinkFeedback">
<Storyboard Storyboard.TargetProperty="Opacity">
<DoubleAnimation From="1.0" To="0.3" Duration="0:0:1" RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="SB_BlinkFeedback" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Another dependency property FeedbackBlink shall be used to start and stop blinking of only one of the images (FeedbackImage). Unfortunately I cannot find out how to address this image from the Storyboard "SB_BlinkFeedback". How could I do that?
Found a working solution shortly after posting the question: I had to move the DataTrigger from <Style.Triggers> to <ControlTemplate.Triggers>. Then the Storyboard declaration can be completed by Storyboard.TargetName="FeedbackImage" without compiler or runtime error.

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 Trigger animation when Visibility is changed?

Well i have a custom control and when Visibility is changed to Visible I have a Trigger with a enter/exit action but the problem is that when the exit action fires the Visibility is no longer Visible so the animation can't be seen how would I fix this?
here is my Trigger:
<ControlTemplate.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource Hide}"/>
</Trigger.ExitActions>
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Show}"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
I tried this too and failed. I think it is not possible to accomplish this in a simple ControlTemplate with a Trigger on the Visibility property. What you can do is add an Opacity animation From 1 To 0 to a Trigger for a different property, for instance a DependencyProperty that you add in the code behind yourself.
You could also use ObjectAnimationUsingKeyFrames to set Visibility for animation period.
In such case there is no need in any codebehind.
There is a way to achieve it. Not 100 % pure, but works for me:
Don't use Visibility property, but use Opacity and Tag property.
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border CornerRadius="5"
BorderThickness="2"
BorderBrush="DodgerBlue"
Background="#CC4f9dea" >
<Grid>
<ContentPresenter HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Stretch" />
<Button x:Name="btnClose" Opacity="0" Content="X" Style="{StaticResource RoundedButtonStyle}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Tag" TargetName="btnClose" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<Style x:Key="RoundedButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="15" Background="White" BorderThickness="1" Padding="2" BorderBrush="Black">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Tag" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.0" To="0.5" Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.5" To="0.0" Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>

Resources