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).
Related
I've made an opacity animation using Blend on one my two UserControls, deleted <UserControl.Resources>, <UserControl.Triggers> and Storyboard.TargetName from that, placed it in App.xaml and it looks like:
<Storyboard x:Key="Loaded">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" >
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
when I call it in code like this before setting the Content of my ContentControl:
Storyboard sb = FindResource("Loaded") as Storyboard;
sb.begin(uc1);
content.Content = uc1;
//and
Storyboard sb = FindResource("Loaded") as Storyboard;
sb.begin(uc2);
content.Content = uc2;
it works as expected. For the transform animation, I've deleted the TransformGroup as well in addition to those above and now it looks like:
<Storyboard x:Key="Unloaded">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="-800"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
If I call it in the same way, I get this error:
System.InvalidOperationException: ''[Unknown]' property does not point to a DependencyObject in path '(0).(1)[3].(2)'.'
How to fix the problem?
You will need to add a RenderTransform to your UserControl which is similar to the Storyboard.TargetProperty which currently expects a TransformGroup with 4th child as TranslateTransform.
Add the below code to each of your two UserControls:
<UserControl x:Class="YourUserControl"
...>
<UserControl.RenderTransform>
<TransformGroup>
<RotateTransform/>
<ScaleTransform/>
<SkewTransform/>
<TranslateTransform/>
</TransformGroup>
</UserControl.RenderTransform>
I have a program where the user needs to navigate through back and forth. Basically there's {Dashboard, Settings ... }
I implemented them using a User Control, so I have my Window, and my User Controls that load in it.
The page switcher code I used to navigate between them I found there (azerdark.wordpress.com/2010/04/23/multi-page-application-in-wpf)
What I want is to animate the way the User Controls navigate when they are loaded in my window.
The simplest thing I found would be to create a Frame over my Window and animate it .. which I did, but I'm having trouble raising the event that fires the animation. I tried animating on window loaded just for testing and it works fine, but when the User Controls are loaded in my windows the animation is not working.
Here's my code
XAML: {PageSwitcher - My Window where the User Controls are loaded}
<Window.Resources>
<Storyboard x:Key="FadeLeft">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="myFrame">
<EasingDoubleKeyFrame KeyTime="0" Value="900">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut" Exponent="5"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut" Exponent="5"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="myFrame">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.6" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="myFrame">
<EasingDoubleKeyFrame KeyTime="0" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase EasingMode="EaseOut" Power="2"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase EasingMode="EaseOut" Power="2"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Frame x:Name="myFrame" Content="" Background="#FF8F4646" Visibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled" RenderTransformOrigin="0.5,0.5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="x:Static local:PageSwitcher.ainmate">
<ei:ControlStoryboardAction Storyboard="{StaticResource FadeLeft}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Frame.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Frame.RenderTransform>
</Frame>
XAML.cs
public static readonly RoutedEvent animate = EventManager.RegisterRoutedEvent(
"Fade", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(PageSwitcher));
public event RoutedEventHandler Fade
{
add { AddHandler(animate, value); }
remove { RemoveHandler(animate, value); }
}
public void RaiseAnimateEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(PageSwitcher.animate);
RaiseEvent(newEventArgs);
}
The event for the animation gets raised in my Switcher class just before Navigate is called to load the content of the User Control into my Window
XAML.cs {Switcher -- only sets the content of my window to the User Control passed when Navigate is called.}
public static void Switch(UserControl newPage)
{
pageSwitcher.Navigate(newPage);
pageSwitcher.RaiseAnimateEvent();
}
Is there anything wrong with my code? Why isn't the animation working?
The frame doesn't show when I navigate between the UserControls
Thanks for any help
I am trying to add a initial animation when items are added to a list box.
For this I'm using Layoutstates, generated by blend inside the itemcontainerstyle:
<VisualStateGroup x:Name="LayoutStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="AfterLoaded"/>
<VisualState x:Name="BeforeLoaded">
<Storyboard>
<DoubleAnimation Duration="0" To="35" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="BeforeUnloaded">
<Storyboard>
<DoubleAnimation Duration="0" To="0.85" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0.85" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
my listbox looks like this:
<ListBox Grid.Row="1" ItemsSource="{Binding Days}" x:Name="Days"
HorizontalAlignment="Stretch"
SelectedItem="{Binding CurrentDay, Mode=TwoWay}"
ItemTemplate="{StaticResource TimeRecordByDayItemTemplate}"
ItemsPanel="{StaticResource ByMonthDaysItemsPanelTemplate}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource DayListBoxItemStyle}" />
I just don't get any animation even though i was just following the channel9 tutorials step by step!
This is the first problem with my state manager, I am also encountering problems with datatriggers, that should go to a state when certain conditions are met, where some do work and some do not, but all of my bindings are correct!
Also all the animations do work in Expression Blend preview.
I can't make out the problem, I've had this so often with silverlight and animations copied from the simplest samples not working in the own environment (look here at channel 9)...
Thanks for you help!
Sounds like you need to add the items one by one once everything is loaded. Seems like a simple solution would be this in your view model:
public class MyViewModel
{
private string[] _items;
private ObservableCollection<string> _itemCollection;
public MyViewModel()
{
// meets requirement of loading items in constructor
_items = { "Johnny", "Thommy", "Jay", "Wommy" };
}
public ObservableCollection<string> Items
{
get
{
if (_itemCollection == null)
{
_itemCollection = new ObservableCollection<string>();
Dispatcher.Invoke(() => LoadItems());
}
return _itemCollection;
}
}
private void LoadItems()
{
foreach (var item in _items)
{
ItemCollection.Add(item);
}
}
}
Basically when the ListBox sets up the binding you queue up the adding of the items to the collection. This should have the items loaded at the right time so that they trigger your animations.
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)
{
...
}
i will post my source code i have at the moment and explain my problem after that.
this is the window where i want the transition to happen
<Window x:Class="MyApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyApp" Height="600" Width="800">
<StackPanel>
<Button Content="View1" Command="{Binding View1Command}"/>
<Button COntent="View2" Command="{Binding View2Command}"/>
<ContentControl Content="{Binding MyContent}"/>
</StackPanel>
This is the associated View-Model
public class MainViewModel
{
public RelayCommand View1Command{ get; private set;}
public RelayCommand View2Command{ get; private set;}
//INotifyPropertyChanged is implemented of course
public ViewModelBase MyContent { get; set;}
public MainViewModel()
{
View1Command = new RelayCommand(() => { MyContent = new ViewModel1();});
View2Command = new RelayCommand(() => { MyContent = new ViewModel2();});
}
}
In the app.xaml i used a data template to associate ViewModel1 with View1 and ViewModel2 with View2. That all works as expected. When clicking the buttons, the view changes, databinding works and everything is ok.
What i like to do now is to use an animation when the view changes.
what do i have to do to animate the transition between the two views with let's say a fade in animation for the new view. the old view disappears and the new one is faded in within one second.
hope you can help me.
best regards
pascal
p.s. if my approach with the data template does not make sense and animation is impossible with that approach i am open for other best practices to change from onw view to another. the solution i use is taken from the wpf demo app by karl shifflet, but i have no idea if that's the right solution for the thing i try to do.
<Window.Resources>
<Storyboard x:Key="OnClick1">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CC1">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CC2">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnClick2">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CC1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CC2">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button">
<BeginStoryboard x:Name="OnClick1_BeginStoryboard" Storyboard="{StaticResource OnClick1}"/>
<BeginStoryboard x:Name="OnClick2_BeginStoryboard" Storyboard="{StaticResource OnClick2}"/>
</EventTrigger>
</Window.Triggers>
<StackPanel>
<Button x:Name="button" Content="View1" Command="{Binding View1Command}"/>
<Button Content="View2" Command="{Binding View2Command}"/>
<Grid>
<ContentControl Name="CC1" Opacity="1" Content="{Binding MyContent}"/>
<ContentControl Name="CC2" Opacity="0" Content="{Binding MyContent}"/>
</Grid>
</StackPanel>
i changed my approach for the transition by using the view model locator for binding view to viewmodel and the visual state manager for transition.