How to create a single style for multiple mouse over events? - wpf

I have four buttons and four text boxes where each button is linked to one of the textblocks. When the mouse is over the button I want the corresponding textblock to fade in (and out on mouse leave). There are plenty of examples of this showing a single button and textblock where you can simply bind a datatrigger to the button name in the textblock style.
Here's what I've got so far (all of this is in a textblock style):
<DataTrigger Binding="{Binding ElementName=UpdateButton, Path=IsMouseOver}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty ="Opacity" Duration="00:00:01">
<DoubleAnimation From="0" To="1" Duration="00:00:01"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty ="Opacity" Duration="00:00:01">
<DoubleAnimation From="1" To="0" Duration="00:00:01"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
As of right now, when I mouse over the Update Button, all of the textblocks show instead of just the one associated with the Update Button.
To fix this I could create styles for each textblock by their name and bind to the appropriate button, but this is a huge amount of repetition. I could likely used "BasedOn" to separate the button binding, but then we're still duplicating all of the code for the Storyboards and whatnot. But does anyone know a better way?
It would seem like there should be a way create this all in a single style using a single generic binding but link the specific buttons to their textblocks, so the button only triggers the Storyboard for it's linked textblock. Anyone know how to do this, or a better way?

A good way to handle this is to create a custom inherited TextBlock that can store reference to a button.
Example
using System.Windows;
using System.Windows.Controls;
//Custom TextBlock
public class SpecialTextBlock : TextBlock
{
//This will be the button reference
public Button BoundButton { get; set; }
//Register the BoundButton as a dependency to allow binding
public static readonly DependencyProperty ButtonProperty = DependencyProperty.Register
(
"BoundButton",
typeof(Button),
typeof(SpecialTextBlock),
new FrameworkPropertyMetadata(default(Button))
);
}
Now that your new SpecialTextBlock is set up, you can create a new style for it. Use your original style, but apply it to TargetType="local:SpecialTextBlock" instead of TargetType="TextBlock".
Then update your DataTrigger from your example within the style so that the trigger binds to itself (the SpecialTextBlock), and then looks at the referenced Button path.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=BoundButton.IsMouseOver}" Value="True">
...
Now you are set up and can create your TextBlocks like so without having to restyle.
//Set your BoundButton binding to specify which button triggers the animation.
<local:SpecialTextBlock BoundButton="{Binding ElementName=UpdateButton}" />

<StackPanel>
<Button x:Name="MouseTarget"
Content="Mouse Over This"
/>
<Button Content="This one changes...">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MouseTarget, Path=IsMouseOver}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>

Related

WPF Source Name property cannot be set within Style. Triggers section Error

I'm getting an error with the following details:
Source Name property cannot be set within Style. Triggers section
<Rectangle Margin="121,163,0,248" HorizontalAlignment="Left" Width="33" Height="34">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"></Setter>
<Style.Triggers>
<EventTrigger SourceName="myButton" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Fill.Color" To="Orange"
Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
BeginTime="0:0:0">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
I want to change the rectangle fill color with Color Animation tag when click on button.
Like it says, you cannot use source name in a style like that.
You can use a data trigger instead. Set say a bool property in your viewmodel from your button's command of click.
Then start your storyboard with a datatrigger binding that bool property and comparing value.
You can probably easily Google datatrigger and storyboard but here's a so question includes an example.
WPF Data Triggers and Story Boards
Btw.
Routed events are rarely very useful IME. Binding icommand is way more practical. Usually.
Edit:
Here's a quick and dirty sample using a togglebutton. Since this approach uses binding it can reference controls by name. Binding is resolved at run time.
<Grid>
<Rectangle>
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=StartStop}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ColourStoryboard">
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Fill.Color" To="Orange"
Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="ColourStoryboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<ToggleButton Content="Start Stop"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Name="StartStop"/>
</Grid>
</Window>
Paste the grid inside a mainwindow, spin it up. When you click the togglebutton it sets ischecked true so the rectangle animates to orange and back to blue. When you click the button again, ischecked becomes false and the animation stops.
You could instead write code in a button handler that set a bound property which is in the datacontext and bind the datatrigger to that bound property. That's what the markup in the link is doing with IsBusy.

Blink animation style for period of time

So i have StackPanel that i want to show with blink Style for several seconds and after that i want it to disappear.
I do not want it to be Automatically but control it from code behind:
So currently this is what i have so far:
<Style x:Key="FaderStyle" TargetType="{x:Type StackPanel}">
<Style.Resources>
<Storyboard x:Key="FadeStoryboard">
<DoubleAnimation Storyboard.TargetProperty="(StackPanel.Opacity)"
From="0"
To="1" Duration="0:0:0.7"
RepeatBehavior="0:0:5"
AutoReverse="True"/>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeStoryboard}"/>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
Code behind:
StackPanel sp;
Storyboard storyboard = Resources["FaderStyle"] as Storyboard;
if (storyboard != null)
storyboard.Begin(sp);
So currently my StackPanel Visibility is Collapsed and after i start the Animation i still cannot see it.
Your code is fine. But your approach to starting the animation is wrong. The trigger starts the animation when Visibility changes to Visible. Not the other way around (which your last piece of code indicates) starting the animation does not change visibility because you didn't write the logic to do that
So with your given code, you need to change the visibility in order to start the animation:
StackPanel sp;
sp.Visibility = Visibility.Visible;
Note that the animation only starts when it enters Visible state. Which means you need to make it collapsed or hidden first.

WPF/Silverlight: How to DataTrigger a Storyboard Animation in MVVM?

I have a boolean property called IsLoginWrong, I want to then play a storyboard animation if the IsLoginWrong is true. (IsLoginWrong does an OnPropertyChanged event, so I know the binding is ok) But I'm having a hard time with the syntax. This might not even be right, but I think datatriggers can only live in Styles...
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLoginWrong}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource LoginWrong}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
But this throws an exception "A storyboard tree in a Style cannot specify a TargetName"... beause styles canno refer to items specifically.. awesome. so how do I do what I'm trying to do? (play animation if a boolean changes in mvvm)
Thanks
Within a style you cannot refer to a storyboard name. So the way I got it to work is to shove your storyboard within the actual style:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLoginWrong}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
.... PUT YOUR ACTUAL STORY BOARD IN HERE ...
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Now DataTriggers can either be put into styles or control templates, so there might be a nicer way to do this with control templates. but this is what I came up with for the time being.
One option would be to start the storyboard using the VisualStateManager. The article at http://blogs.infosupport.com/blogs/alexb/archive/2010/04/02/silverlight-4-using-the-visualstatemanager-for-state-animations-with-mvvm.aspx explains how to control the current state of the VisualStateManager from the view model using an attached property.

Get WPF resource from code?

What is the simplest way to get a WPF resource from code?
I need to disable a text box in a WPF form if a checkbox in the same window is checked. I have wired the checkbox to an event handler in code-behind. The event handler disables the checkbox and changes its background to a light gray, to indicate that the control is disabled:
private void OnCheckBoxChecked(object sender, RoutedEventArgs e)
{
MyTextBox.IsEnabled = false;
MyTextBox.Background = (Brush)FindResource("DisabledControlBackgroundBrush");
}
The disabled control background color is defined as a resource in a resource dictionary that is imported into the WPF window. I tested the resource by setting the textbox background in XAML, and the resource works fine.
I also know the event handler is working, because it disables the text box when the checkbox is clicked.
My problem is that the event handler isn't changing the Background property as it should. I suspect that there is a problem with my call to FindResource, but I am not getting an exception, and the Output window has nothing on it.
So, how do I get this resource from code and apply it to my text box? Thanks for your help.
David. I've put together a sample window that does this using triggers on the TextBox.Style:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Window.Resources>
<SolidColorBrush x:Key="IsCheckedColor" Color="DarkGray" />
</Window.Resources>
<StackPanel>
<TextBox x:Name="textbox" Margin="36" Height="24" >
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=checkbox}" Value="True" >
<Setter Property="Background" Value="{StaticResource IsCheckedColor}" />
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox x:Name="checkbox" Content="Click Me" Height="24" Margin="36"/>
</StackPanel>
</Window>
You can't use a Trigger to change another control's properties, but you can use them to change a control's properties based on something else, like a DataContext or another control.
Each control can have a Triggers collection, but this can only contain EventTriggers. In a Style you can use plain Trigger which can be used to control animation, as well as DataTrigger, which I've used in this sample to control the TextBox settings based on the properties of the CheckBox.
Notice that I've also used a Setter outside of the Triggers collection to set the default value, and I don't need a second Setter for when the CheckBox is not checked -- it just goes back to the "default" state.
edit - how to change background of disabled TextBox
I do this in Blend, but if you don't have Blend you can of course put the XAML in directly. This has to do with controls states. As the TextBox transitions among Normal, MouseOver, and Disabled, you can animate changes to the appearance. In this case we use an animation of virtually zero duration, so the change is immediate.
Add the following to the StackPanel:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="textbox" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="{StaticResource IsCheckedColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
this kind of approach is form Windows Forms which is a bad pattern for WPF. All what you want to do is can be done by triggers and styles
Patten:
<Style x:Key="BackGroundCheckBoxStyle"> < !--apply the style to checkbox -->
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="{Binding ElementName=m_txtBox, Path=IsEnabled, Mode=TwoWay}" Value="false}" />
<!-- bind your resource here with a setter as well -->
</Trigger>
</Style.Triggers>
</Style>
Background store in the Aplication.Resources scope or Window. Triggers are Freezable object so GUI will refresh itself (no repaint() needed)
Try to get some overview, since it is a new way to develop apps :)

WPF: Selecting the Target of an Animation

I am trying to create a simple (I think) animation effect based on a property change in my ViewModel. I would like the target to be a specific textblock in the control template of a custom control, which inherits from Window.
From the article examples I've seen, a DataTrigger is the easiest way to accomplish this. It appears that Window.Triggers doesn't support DataTriggers, which led me to try to apply the trigger in the style. The problem I am currently having is that I can't seem to target the TextBlock (or any other child control)--what happens is which the code below is that the animation is applied to the background of the whole window.
If I leave off StoryBoard.Target completely, the effect is exactly the same.
Is this the right approach with the wrong syntax, or is there an easier way to accomplish this?
<Style x:Key="MyWindowStyle" TargetType="{x:Type Window}">
<Setter Property="Template" Value="{StaticResource MyWindowTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2" Storyboard.Target="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}}"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop" From="Black" To="Red" Duration="0:0:0.5" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Update
Should have also mentioned that I tried to name the TextBlock and reference it via StoryBoard.TargetName (as Timores suggested), and got the error "TargetName property cannot be set on a Style Setter."
EDIT: I have overseen the fact that the TextBlock is in the ControlTemplate of your custom Window/Control. I do not think that it is possible to target a control within the ControlTemplate from a Storyboard outside of this ControlTemplate. You could however define a property on your custom Window which you then databind to your ChangeOccurred property, and then add the trigger to your ControlTemplate which will now get triggered by the custom Control's property rather than the Window's ViewModel's property (of course, indirectly it is triggered by the ViewModel because ChangeOccurred is bound to the property of the custom Window which in turn triggers the animation - uh, complex sentence, hope you understand). Is this an option? Could you follow? ;-)
Maybe some code helps:
public class MyCustomWindow : Window
{
public static readonly DependencyProperty ChangeOccurred2 = DependencyProperty.Register(...);
public bool ChangeOccurred2 { ... }
// ...
}
And some XAML:
<local:MyCustomWindow ChangeOccurred2="{Binding ChangeOccurred}" ... >
<!-- Your content here... -->
</local:MyCustomWindow>
<!-- Somewhere else (whereever your ControlTemplate is defined) -->
<ControlTemplate TargetType="{x:Type local:MyCustomWindow}">
<!-- your template here -->
<ControlTemplate.Triggers>
<Trigger Property="ChangeOccurred2" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetName="txtWhatever"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Note: I named the Window's property ChangeOccurred2 because I wanted it to be distinguishable from the ViewModel's ChangeOccurred property. Of course, you should choose a better name for this property. However, I am missing the background for such a decision.
My old answer:
So, you want to animate a TextBlock which is in the content of a (custom) Window?!
Why do you want to set the style on the Window, and not on the TextBlock itself? Maybe you should try something like this (did not test this!):
<local:MyCustomWindow ... >
<!-- ... -->
<TextBlock x:Name="textBlockAnimated" ... >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- ... -->
</local:MyCustomWindow>
The {Binding ChangeOccurred} might not be sufficient. You might have to add a DataContext to the TextBlock, or add a RelativeSource or something.
Is the TextBlock in the MyWindowTemplate ?
If so, give the TextBlock a name and use Storyboard.TargetName to reference it.
See another question in SO

Resources