Trying to port to WPF some behavior from an old Silverlight application that allowed users to configure their view by moving/minimizing/maximizing various UserControls. Several StoryBoards were declared in UserControl.Resources and later accessed from code via their x:Name.
<Storyboard x:Name="MaximizeStoryboard" Completed="MaximizeStoryboard_Completed" Storyboard.TargetName="WholeControl">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(FrameworkElement.Width)">
<SplineDoubleKeyFrame x:Name="MaximizeWidth" KeyTime="00:00:00.3000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
...
In WPF, after changing x:Name to x:Key, I can access the Storyboard via:
Storyboard maximizeStoryboard = (Storyboard)Resources["MaximizeStoryboard"];
Previously used
MaximizeWidth.Value = Container.Contents.ActualWidth;
to set the restore width (and other values) from code, before beginning the animation. That doesn't work in WPF. What is the proper way to get access so those values can be set from code?
One of the possible way can be go through the children of Storyboard, find DoubleAnimationUsingKeyFrames child and then get the first KeyFrame.
Storyboard maximizeStoryboard = (Storyboard)Resources["MaximizeStoryboard"];
var doubleAnimationUsingKeyFrames = maximizeStoryboard.Children.OfType<DoubleAnimationUsingKeyFrames>().FirstOrDefault();
if (doubleAnimationUsingKeyFrames != null)
{
if (doubleAnimationUsingKeyFrames.KeyFrames.Count > 0)
{
var keyFrame = doubleAnimationUsingKeyFrames.KeyFrames[0] as SplineDoubleKeyFrame;
if(keyFrame !=null)
{
keyFrame.Value = Container.Contents.ActualWidth;
}
}
}
Related
I subscribed to the wpf window's Loaded event: Loaded += loaded; and try to change the opacity of some controls in code behind.
I notice that in the method loaded the controls are not painted by wpf yet. So the code has no effect, the rendering of the controls occurs only after the method is exited.
1) Is there another event e.g. Rendered that I can subscribe to?
EDIT: I just discovered that there is an OnContentRendered event and the following code works:
Although an animation is probably preferrable.
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
for (int i = 0; i < 100; i++)
{
Parentpanel.Opacity += 0.01;
Splashscreen.Opacity -= 0.01;
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
Thread.Sleep(50);
}
}
Otherwise I probably have to use an animation that changes the opacity of usercontrol1 from 0.1 to 1.0 and of usercontrol2 from 1.0 to 0.0.
2) Do you know an example for such an animation?
In your Loaded handler you can post the UI altering operation ( e.g. void ChangeOpacity() ) on the dispatcher:
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ChangeOpacity));
It will execute after the rendering is done.
Edit
I see you simply need an animation to start when the window opens. It's easily done in XAML, here's a working example generated in Blend:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="200">
<Window.Resources>
<Storyboard x:Key="myStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="myControl">
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource myStoryboard}"/>
</EventTrigger>
</Window.Triggers>
<StackPanel>
<TextBox x:Name="myControl" Text="I'm disappearing..." />
</StackPanel>
</Window>
I just recently was having issues while trying to render some standardized animation to WPF usercontrols when the visibility changed. In my app, I have a couple singleton static classes. In one, I added a static method "VisibleFader" and you pass in the framework element control and it automatically attaches the event handler to the double-animation against the opacity property. It works great and does not require ANY changes to any other Styles, Control Templates, or any other theme implementations.
public static DoubleAnimation da;
public static void VisibleFader(FrameworkElement fe)
{
if (da == null)
{
da = new DoubleAnimation();
da.From = 0;
da.To = 1;
da.Duration = new Duration(TimeSpan.FromSeconds(.7));
}
fe.IsVisibleChanged += myFader;
}
private static void myFader(object sender, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)sender).BeginAnimation(FrameworkElement.OpacityProperty, da);
}
Then, in my class (such as your Loaded Event), I just call this static method with that "userControl" object.
MySingletonClass.VisibleFader( this.whateverUserControl );
Done... So, when the visibility changes, it fades IN from nothing to 1. And if something was having its visibilty to hidden, it's gone anyhow.
In my Application.Resources I have the following Storyboard defined.
<Application.Resources>
<!--Storyboard animation for fading out a UI element-->
<Storyboard x:Key="FadeOutAnimation">
<DoubleAnimation From="1"
To="0"
Duration="0:0:0.25"
Storyboard.TargetProperty="Opacity"
AutoReverse="False" />
</Storyboard>
</Application.Resources>
In code-behind I'm using this to fade out some TextBlocks when the user taps on them.
// Get the storyboard from application resources
Storyboard sb = (Storyboard)App.Current.Resources["FadeOutAnimation"];
// Setup the animation target for fade out
Storyboard.SetTarget( sb.Children.ElementAt( 0 ) as DoubleAnimation, myTextBlock );
// Set the animation completed handler
sb.Completed += ( s, e1 ) => {
// Stop the Storyboard
sb.Stop();
// Hide the TextBlock
myTextBlock.Visibility = Visibility.Collapsed;
};
// Start the Storyboard
sb.begin();
The question is, do I need to somehow 'unhook' myTextBlock from being the target of the DoubleAnimation?
If yes, how do I do it?
The reason I'm asking is I'm worried about a reference to that TextBlock hanging around until this Storyboard is used again.
Thanks for your help!
We don't always have to use Xaml in sliverlight if its getting in our way:-
public static AnimationHelper
{
public static void FadeOutAndCollapse(UIElement target)
{
DoubleAnimation da = new DoubleAnimation();
da.From = 1.0;
da.To = 0.0;
da.Duration = TimeSpan.FromSeconds(0.25);
da.AutoReverse = false;
StoryBoard.SetTargetProperty(da, new PropertyPath("Opacity"));
StoryBoard.SetTarget(da, target);
StoryBoard sb = new StoryBoard();
sb.Children.Add(da);
EventHandler eh = null;
eh = (s, args) =>
{
target.Visiblity = Visibility.Collapsed;
sb.Stop();
sb.Completed -= eh;
}
sb.Completed += eh;
sb.Begin();
}
}
With this in place you can fade out and collapse any UI element with:-
AnimationHelper.FadeOutAndCollapse(myTextBox);
I'd been inclined to remove the From = 1.0 to make it more general so that elements that have a lower starting opacity don't suddenly flash to full opacity before disappearing.
Don't worry about dangling references to lightweight user interface elements; they will be garbage collected when there are no more references. Your more pressing problem is that a single storyboard is being used for multiple text objects and if they overlap, it will do the wrong thing.
For example, if you start one animation and then start another, then they will both stop at the same time because there is only one storyboard and your handler calls stop. Either associate a separate storyboard with each text element in XAML or create a new storyboard in the code-behind for each animation that you want to do.
Also, if you were to use a single storyboard, you should be careful to remove your completed event handler because currently you will keep accumulating them and old handlers will be called when the storyboard re-completes.
My application has a few background worker, each doing different work. When I click the 'Start' button, all backgroundworker will start simultaneously.
in my xaml, I had defined my animation of a rotate image:
<window.Resources>
<Storyboard x:Key="imageRotate">
<DoubleAnimation Storyboard.TargetName="transRotate"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
From="0" To="360"
Duration="0:0:0.5"
AutoReverse="False"
RepeatBehavior="Forever"/>
</Storyboard>
</window.Resources>
I want the animation to begin when all the backgroundworker started, and stop ONLY after ALL background worker stopped.
I have a property call AreWorkersBusy:
private bool _areWorkerBusy;
public bool AreWorkerBusy
{
get
{
return _areWorkerBusy;
}
set
{
bool isBusy = false;
foreach(BackgroundWorker worker in BackgroundWorkerList)
{
if(worker.IsBusy)
isBusy = true;
}
_areWorkerBusy = isBusy;
}
}
but it's not dependencyProperty, so I can't bind to my animation's DataTrigger.
Any workaround???
Help!
One of the possible ways,
You can implement INotifyPropertyChanged to notify 'AreWorkerBusy' changes, create a dependency property in the control and bind 'AreWorkerBusy' with it.
Create two routed events one to start animation and another to stop animation.
In the property changed handler for your DP, raise the specific routed event.
In your control write event triggers, and based on the event start of stop the animation.
I had written a similar experience, http://keepitsimpleengineer.blogspot.com/2010/09/wpf-circular-progress-control-part-2.html
I'm not hugely familiar with a lot of WPF yet, so if this query betrays some clear misunderstandings I'd appreciate them being pointed out.
What I'm trying to do (for good or ill) is synchronise a DoubleAnimation.FromProperty with the actual width of a StackPanel containing/running the animation.
I originally tried doing this with data binding, e.g.
BindingOperations.SetBinding(anim,
DoubleAnimation.FromProperty,
new Binding {Source = panel, Path = new PropertyPath(ActualWidthProperty)});
But this doesn't seem to work. Although I can bind a text box to the From property and see that it's changing, the animation continues to run from the initial value. Is this anything to do with story board freezing? (which I know nothing about but have just heard of)
Then I thought why not stop and restart the story board (turn it off and on again!), changing the FromProperty in the meantime, by handling the StackPanel.SizeChanged event. Something like this :
void panel_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (myStoryBoard != null && myStoryBoard.GetCurrentState(panel) == ClockState.Active)
{
myStoryBoard.Stop(panel);
foreach(var child in myStoryBoard.Children)
{
var anim = child as DoubleAnimation;
if (anim == null) continue;
anim.From = panel.ActualWidth;
}
myStoryBoard.Begin(panel, true);
}
}
This works, but, of course, it starts the animation from scratch each time the panel is resized. What I'd prefer is to be able to resume the animation from the point it was interrupted but with a new FromProperty. Is this even possible?
It is a lot easier than you think. The cool thing about Animations in WPF and Silverlight is that they can be relative to the current situation. The only thing you need to do to create a relative animation is creating for example the DoubleAnimation with To filled in but do NOT fill in the From.
Example below is to animate the opacity of a stackpanel from the current value to 1:
<DoubleAnimation Duration="0:0: To="1"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="StackPanel.Opacity" />
EDIT1:
Make the animation loop back to its original value without using the from and loop forever
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<DoubleAnimation Duration="0:0:2"
To="1"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="StackPanel.Opacity"
/>
</Storyboard>
I have the following storyboard:
<Window.Resources>
<Storyboard x:Key="ButtonsAnim">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="topRightButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="-100"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="topRightButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="100"/>
...
It basically moves some buttons around in a canvas.
This is the code that starts the animation:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Storyboard sb = (Storyboard)Resources["ButtonsAnim"];
storyBoard = sb;
storyBoard.Begin(this, true);
}
What I am trying to do is to reset the animation when I click a button which hides the window. When the window reappears the animation should start from the beginning.
I tried using storyBoard.Begin(this, true) when the application reappears but for this first milliseconds the buttons are at their last position.
I then tried storyBoard.seek(TimeSpan.Zero) before hiding the window but it fails:
System.Windows.Media.Animation
Warning: 6 : Unable to perform action
because the specified Storyboard was
never applied to this object for
interactive control.; Action='Seek';
Storyboard='System.Windows.Media.Animation.Storyboard';
Storyboard.HashCode='24901833';
Storyboard.Type='System.Windows.Media.Animation.Storyboard';
TargetElement='System.Windows.Media.Animation.Storyboard';
TargetElement.HashCode='24901833';
TargetElement.Type='System.Windows.Media.Animation.Storyboard'
I also tried storyBoard.remove(this) before hiding the window, same effect: the buttons are at their last position.
Any ideas?
Thank you.
To use StoryBoard.Remove() this way - you should keep reference to your storyboard object.
Like this:
Storyboard myStoryBoard;
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
myStoryBoard = (Storyboard)Resources["myStoryBoard"];
myStoryBoard.Begin();
}
void sbRemoveEvent()
{
myStoryBoard.Remove();
}
I think Storyboard.Stop() should work here. But if you not find anything elegant, you can try reset buttons' transform after you've hided the window. E.g.:
((TranslateTransform)((TransformGroup)topRightButton.RenderTransform)[3]).X = 0;
((TranslateTransform)((TransformGroup)topRightButton.RenderTransform)[3]).Y = 0;
Hope I didn't make any mistake while casting.
NB: You may also find useful this example from MSDN: How to: Control a Storyboard After It Starts