Accessing a style's storyboard of a button - wpf

My problem is that I have a Button and I want to access the Storyboard, which is a part of the assigned style.
<Button x:Name="pictureFolderButton" Content="Pictures" Style="{StaticResource ImageTileButtonStyle}" Click="pictureFolderButton_Click" />
The style is very comprehensive so I'll post only a part of it:
<Style x:Key="ImageTileButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnLoaded1"/>
</ControlTemplate.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
...
</VisualStateGroup>
<VisualStateGroup x:Name="AnimationStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ExpandedFull">
<Storyboard x:Name="expandStoryBoard" >
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter RecognizesAccessKey="True" VerticalAlignment="Stretch" Margin="0,47,0,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I just want to get notified when the "ExpandedFull" animation has ended. Therefore, I thought I have to get the "expandStoryBoard" programmatically and add a Completed event handler.
The only thing I got working is to access the button's style at runtime:
Style style = pictureFolderButton.FindResource("ImageTileButtonStyle") as Style;
How do I have to proceed?
Thank you very much!

In theory you should be able to go down the visual and logical tree of your button to get to the storyboard, but that is rather tedious, if you name the Grid in the template "grid", something like the following might work:
Grid grid = pictureFolderButton.FindName("grid") as Grid;
IList groups = VisualStateManager.GetVisualStateGroups(grid);
VisualStateGroup targetGroup = null;
foreach (var group in groups)
{
if (group is VisualStateGroup && (group as VisualStateGroup).Name == "AnimationStates")
{
targetGroup = group as VisualStateGroup;
break;
}
}
if (targetGroup != null)
{
IList states = targetGroup.States;
VisualState targetState = null;
foreach (var state in states)
{
if (state is VisualState && (state as VisualState).Name == "ExpandedFull")
{
targetState = state as VisualState;
break;
}
}
if (targetState != null)
{
targetState.Storyboard.Completed += new EventHandler(Expansion_Completed);
}
else throw new Exception("VisualState not found.");
}
else throw new Exception("VisualStateGroup not found.");
Another way that comes to mind is extracting your storyboard to a resource, but i am not sure if this will have any side effects, i.e.:
<ControlTemplate.Resources>
...
<Storyboard x:Key="expandStoryBoard" x:Name="expandStoryBoard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
...
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}"/>
Then you should be able to use FindResource on the button to get the storyboard.
Hopefully some of that works or at least helps a bit.

Just try with this with StoryBoard name "OnLoaded1":
<Button Height="75" Width="120" Style="{StaticResource ImageTileButtonStyle}" Click="Button_Click" >Hello</Button>
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn=(Button)sender;
Storyboard stb = btn.TryFindResource("OnLoaded1") as Storyboard;
}

If you add the Storyboard to the resources, you can set the event handler for the Timeline.Completed event in the XAML file and implement the handler in the corresponding class.
Define the Storyboard in the Resources section of your control like this:
<UserControl.Resources>
<Storyboard x:Key="expandStoryBoard" Completed="OnExpandCompleted"> ... </Storyboard>
...
</UserControl.Resources>
Reference the Storyboard as a static resource:
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}" />
Implement the Completed event handler in the corresponding class:
void OnExpandCompleted(object sender, EventArgs e)
{
...
}

Related

I can not dynamically GoToState in WPF. What is wrong?

I created the simplest WPF application with Blend for Visual Studio.
I created the simplest visual state groups for a single textbox:
My problem is that I can not dynamically GoToState using this code:
public MainWindow()
{
InitializeComponent();
textBox.MouseEnter+=new System.Windows.Input.MouseEventHandler(textBox_MouseEnter);
}
private void textBox_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
// This will be an empty list
var states = VisualStateManager.GetVisualStateGroups(textBox);
// This will be false
bool thisReturnsFalse = VisualStateManager.GoToState(textBox, "VisualState1", true);
}
This is my XAML:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="VisualState">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="textBox">
<EasingColorKeyFrame KeyTime="0" Value="#FFE40404"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="VisualState1">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="textBox">
<EasingColorKeyFrame KeyTime="0" Value="#FF23FF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<ei:GoToStateAction StateName="VisualState1"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyDown">
<ei:GoToStateAction StateName="VisualState"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Your VisualStateGroups are not defined in the TextBox, so the call
var states = VisualStateManager.GetVisualStateGroups(textBox);
won't return what you expect. If you want to get the defined states, pass the FrameworkElement that contains the VisualStateGroups (I'm assuming the Window here):
var states = VisualStateManager.GetVisualStateGroups(this);
The same should probably be used for the call to VisualStateManager.GoToState:
bool shouldReturnTrue = VisualStateManager.GoToState(this, "VisualState1", true);

Silverlight: How do you implement validation in custom controls?

How do you implement validation in custom controls? I am looking to replicate the standard validation logic you would see with a TextBox data-bound to a model or view-model that exposes IDataErrorInfo or INotifyDataErrorInfo.
To implement validation you should add the "ValidationStates" group to the VisualStateManager of the control.
I will illustrate the simple custom control TestControl with the TestProperty property.
Style in the Generic.xaml, depending on the state displays the blue text or the red border with the first error message:
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock Text="{TemplateBinding TestProperty}" Foreground="Blue" />
<Border x:Name="InvalidBorder" BorderBrush="Red" BorderThickness="2" Visibility="Collapsed">
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource TemplatedParent}}" Foreground="Red" FontWeight="Bold" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There are 3 states:
Valid - No validation errors.
InvalidFocused - Applied when you set the focus to the control in the invalid state. Default controls display the red popup as well as the red border in this state, but in my particular example I don't display it for simplicity. Users can invoke this state by using the Tab keyboard button or by clicking a focusable inner control like TextBox.
InvalidUnfocused - Applied when the control in the invalid state but isn't focused.
Here is the code of the control, it contains only one property:
public class TestControl : Control
{
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public string TestProperty
{
get { return (string)GetValue(TestPropertyProperty); }
set { SetValue(TestPropertyProperty, value); }
}
public static readonly DependencyProperty TestPropertyProperty =
DependencyProperty.Register("TestProperty", typeof(string), typeof(TestControl), new PropertyMetadata(null));
}
After that if you use the IDataErrorInfo, the correct xaml is:
<local:TestControl TestProperty="{Binding SomeModelProperty, ValidatesOnDataErrors=True}" />
For the INotifyDataErrorInfo, the correct xaml is even simplier:
<local:TestControl TestProperty="{Binding SomeModelProperty}" />

How to choose which storyboard to run on click of a Radio Button and Next button?

I am looking for the code which will allow running a certain storyboard based on which radio button is selected. The scenario is to select a radio button first and then click ‘Next” button to run storyboard. For example, if the first radio button is selected and “Next’ button clicked, then the first storyboard should be played. Any ideas are highly appreciated. Thank you in advance.
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ClickCount.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0" Value="Red"/>
<EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FF00FFED"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard2">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0" Value="Red"/>
<EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FF5AFF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard3">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0" Value="Red"/>
<EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FFFFF500"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<RadioButton GroupName="Os" Content="Storyboard 1" IsChecked="True" FontSize="16" Margin="10,10,10,0"/>
<RadioButton GroupName="1" Content="Storyboard 2" Margin="10,31,0,0" FontSize="16" />
<RadioButton GroupName="1" Content="Storyboard 3" Margin="10,50,0,0" FontSize="16" />
<Button x:Name="Next" Content="Next>" Width="75.06" Height="23" IsDefault="True"
Click="ClickNextButton" VerticalAlignment="Top" />
<Border x:Name="border" BorderBrush="Black" BorderThickness="1"
HorizontalAlignment="Center" Height="100" VerticalAlignment="Center"
Width="100" Background="Red"/>
</Grid>
C#:
public partial class MainWindow : Window
public MainWindow()
{
this.InitializeComponent();
}
private void ClickNextButton(object sender, System.Windows.RoutedEventArgs e)
{
Counter += 1;
if (Counter == 1) { }
if (Counter == 2) { }
}
}
If you put your Storyboards inside a VisualStateManager you can use the GoToState method:
VisualStateManager.GoToState(this, "Storyboard1", useTransitions);
Your XAML will become something like this:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="MyStates">
<VisualState Name="Storyboard1">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0" Value="Red"/>
<EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FF00FFED"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
....
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Note that the name has moved from the Storyboard to a VisualState.
Your code behind is then something like this:
if (Counter == 1)
{
VisualStateManager.GoToState(this, "Storyboard1", useTransitions);
}
if (Counter == 2)
{
VisualStateManager.GoToState(this, "Storyboard2", useTransitions);
}
(Though this is from memory so there might be syntax errors)

Add image silverlight hovereffect from codebehind

I have a stackpanel that has a dynamic amount of images that are added programatically, Is there a way I can set a hover/click effect programatically on these images. I would like for the image to "glow" when clicked. How do i do that in silverlight? I've noticed the Image.Effect property, but I'm not really sure how to use it.
What you need to do is create a new usercontrol with the image control inside with the visualstates attached to it. This way, you can dynamically add the usercontrol to the stackpanel and have the animations being called without attaching them by events from your main app.
I used the DropShadowEffect on the Image.Effect to create a pulsating animation.
For eg. This is inside your usercontrol:
XAML
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ImageState">
<VisualState x:Name="NormalImageState">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.BlurRadius)" Storyboard.TargetName="image1" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="GlowingImageState">
<Storyboard AutoReverse="True">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.BlurRadius)" Storyboard.TargetName="image1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="20"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Image Name="image1" MouseEnter="image1_MouseEnter" MouseLeave="image1_MouseLeave" >
<Image.Effect>
<DropShadowEffect Color="Red" BlurRadius="0" ShadowDepth="0"/>
</Image.Effect>
</Image>
C#
public ImageSource ImageSource
{
get;
set
{
image1.Source = value;
}
}
private void image1_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToState(this, "GlowingImageState", true);
}
private void image1_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToState(this, "NormalImageState", true);
}
Then you can add this usercontrol to your stack panel like so:
MyUC uc= new MyUC(); //control we just created
uc.ImageSource = sourceOfImg; //the source of the intended image
myStack.Children.Add(uc); //adding it to the stackpanel.
Tell me if this works.
You can use a transformation to create an animation to change the colour of your image when it is clicked.
Have a look at the MSDN page: Animation Overview. This page includes details on how to do this programmatically (Creating an Animation in Procedural Code).

Small animations in Silverlight

I've made a simple storyboard that takes a particular ListBoxItem and lets it grow by a factor of 1.3. I'd like to add this animation to every ListBoxItem I create dynamically so that it can be activated when it gets a mouse-over, but the storyboard seems to be hardcoded to that first item:
<Storyboard x:Name="ListItem_MouseEntered">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RecentNews" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.3"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RecentNews" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
How should I go about duplicating this storyboard and setting the target to every listboxitem?
Cheers
Nik
PS, I believe I have some errors in the animation, don't worry about that, it's not part of my question :-)
You can define a ControlTemplate for ListBoxItem in the Resources section of the UserControl like this:
<ControlTemplate x:Key="LIT" TargetType="ListBoxItem">
<Border x:Name="MainBorder" BorderBrush="Red" BorderThickness="2" Background="Yellow" MouseEnter="Border_MouseEnter">
<Border.Resources>
<Storyboard x:Name="ItemStory">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ItemTransform" Storyboard.TargetProperty="ScaleX">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.3"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ItemTransform" Storyboard.TargetProperty="ScaleY">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
<Border.RenderTransform>
<ScaleTransform x:Name="ItemTransform" />
</Border.RenderTransform>
<TextBlock Text="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
Handle the MouseEnter event:
private void Border_MouseEnter(object sender, MouseEventArgs e)
{
Border itemBorder = (Border)sender;
Storyboard itemStory = (Storyboard)itemBorder.FindName("ItemStory");
itemStory.Begin();
}
And use it like this in XAML:
<ListBox x:Name="MyList">
<ListBox.Items>
<ListBoxItem Content="Toto 1" Template="{StaticResource LIT}" />
</ListBox.Items>
</ListBox>
Or like this in C#:
MyList.Items.Add(new ListBoxItem()
{
Content="Toto 2",
Template = (ControlTemplate)Resources["LIT"]
});
If you use the visual state manager, you can apply this to all of type:
This shows how to do just that.

Resources