Why does my Storyboard only run once? - c#

I have a storyboard that I would like to run multiple times. Unfortunately, after it runs the first time, even when this code is hit, it doesn't animate again. It only animates the first time. I'm setting an initial value....even when I set the value before I run it again.
DoubleAnimationUsingKeyFrames animateTransformX = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame initialTransformXKeyframe = new EasingDoubleKeyFrame();
initialTransformXKeyframe.KeyTime = TimeSpan.FromSeconds(0);
//Resetting the value before running it again.
initialTransformXKeyframe.Value = 0;
animateTransformX.KeyFrames.Add(initialTransformXKeyframe);
EasingDoubleKeyFrame animateTransformXKeyframe = new EasingDoubleKeyFrame();
animateTransformXKeyframe.KeyTime = TimeSpan.FromSeconds(0.5);
animateTransformXKeyframe.Value = aDynamicValueGoesHere;
animateTransformX.KeyFrames.Add(animateTransformXKeyframe);
Storyboard.SetTarget(animateTransformX, image);
Storyboard.SetTargetProperty(animateTransformX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(animateTransformX);
myStoryboard.Begin(this);
I feel like it is very simple but for the life of me I can't understand why. Any help would be appreciated.
EDIT
The reason I was doing it from codebehind is because storyboards are freezable and I have had trouble in the past with dynamic values in them. For simplicity I didn't put that I was using a dynamic value in the code; I updated the code above to show I'm using a dynamic value.
I tried adding the storyboard as XAML inside a resource with the use of a dynamic value. It works fine (and replays) if a non-dynamic value is used but it only plays once (the first time) when a dynamic value is used.
So, if {Binding OffsetX} were replaced with 50, for example, this works fine, and repeatedly.OffsetX gets updated through a property that implements INotifyPropertyChanged. Even if it were the same, if it were never updated, I would think it should still animate, just to the same value it animated to before (like it does with 50). But that isn't the case; it just doesn't animate again. So now I'm really lost.
<Storyboard x:Key="InitialAnimate">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)" Storyboard.TargetName="StoryImage">
<EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="{Binding OffsetX}"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
EDIT 2
I didn't fix the problem but I found a workaround that works for my particular situation. I was getting this error in my output window:
System.Windows.Media.Animation Warning: 6 : Unable to perform action because the specified Storyboard was never applied to this object for interactive control.; Action='Stop'; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='28715394'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='6255521'; TargetElement.Type='System.Windows.Controls.Grid'
I found a few StackOverflow answers regarding it, including how to find what Storyboard caused the issue and another unanswered question.
I am still not sure how to fix my original issue, but here's my workaround. Since I only needed the animation to run when you restarted (an event that happens in my viewmodel), I use an event raised in the viewmodel and consumed in the parent view to remove and then re-add the UserControl that contains the animation.
In that UserControl, I trigger the storyboard through a ControlStoryboardAction tied to a Loaded EventTrigger. This makes the animation run when it loads, and it only ever runs the animation once during the life of the UserControl. I am able to use the dynamic values in XAML. Here's how I'm removing and readding the view from it's parent:
Parent View:
<grid x:Name="imageHolder></grid>
Parent Codebehind:
private void CreateAnimations(object sender, EventArgs e)
{
imageHolder.Children.RemoveAt(0);
Views.Image image = new Image();
image.Name = "image";
imageHolder.Children.Add(image);
}

You can't change the Value property at runtime the way you're doing after the edit.
This is because Animations are freezable objects. There is more
information in the MSDN Documentation, but basically it means you
can't use binding because properties in the frozen object (i.e. the
animation) cannot change.
To get around this limitation, you will need to do some or all of the
work in code-behind.
See Why Storyboard value wouldn't get from a resource or binding?
So You probably want to go back to the codebehind method.
So as a final answer, what I would do is when I want to run it again:
Remove the previous storyboard
create a whole brand new Storyboard(or just the animation) and run that.

Related

C#, xamarin, binding many objects

So I've got a bunch of Frame objects that I'm adding to the display via code (the count of them is variable). And being true to MVVM, I need to bind the background color of each to something in the model. So the bind is simple:
Frame f1 = new Frame();
f1.SetBinding(Frame.BackgroundColorProperty, "BackColor1");
Frame f2 = new Frame();
f2.SetBinding(Frame.BackgroundColorProperty, "BackColor2");
And so on. That's just a quick example, the real thing is in a loop. And in the model:
Xamarin.Forms.Color BackColor1
{
get
{
return Color.Black;
}
}
Xamarin.Forms.Color BackColor2
{
get
{
return Color.White;
}
}
And so on. This works fine for a few objects, but what if there's 50? 100? I don't want to create a 100 BackColor properties in the class; that way lies madness. I've only being playing around in C# and xamarin for a few weeks now, so there's much I've still got to learn. What would be the best way to stay true to MVVM when you need to bind a large number of objects like this?
Are those colors gong to change once assigned? If so no need to stay in "true MVVM," which I assume you mean "using bindings." True MVVM does not require using bindings for everything, in fact if a property's value won't change once the object is instantiated, then using a binding is overkill. It is OK to use static values for properties and still be within "true MVVM." In fact static values for properties are better if the values are not going to be changed, or even not changed often, since bindings do have some performance overhead. So basically, every time you instantiate a new Frame, just set the BackgroundColor statically.

WPF Storyboard Completed Event

I'm experiencing a problem where the I am unable to issue a Stop() command to a Storyboard in the Completed event.
For example
private void ShowStandbyGrid()
{
StandbyGrid.Visibility = Visibility.Visible;
Storyboard sbFadeIn = this.FindResource("StandbyGridFadeIn") as Storyboard;
sbFadeIn.Completed += (s, a) =>
{
Console.WriteLine("Completed Event Fired");
sbFadeIn.Stop(this); //<-- This does not cause the storyboard animation to stop
};
sbFadeIn.Begin(this, true);
}
I can see the event is firing, however it doesn't seems want to stop the Storyboard Animation.
Because of this problem, every time when doing something to the UI, this animation will run continuously. I believe it is because the animation never got stopped.
Am I missing something?
UPDATE 1
Here's the xaml for the storyboard
<Storyboard x:Key="StandbyGridFadeIn">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="StandbyGrid">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
UPDATE 2
OK, after 8+ hours of testing and trying, I still have no ideas why it is not working. However, here are several facts (or problem/discovery)
I created a button to run the storyboard in reverse direction. The storyboard executed as expected (the inverse of StandbyGridFadeIn, so more like FadeOut). However, after it is performed, the StandByGridFadeIn automatically executed again without any function calls. When it is done, I saw the console output fire 2 x "Completed Event Fired".
I output GetCurrentState(this); before the sbFadeIn.Stop(this); The output shows "Filling". I then output GetCurrentState(this); after the sbFadeIn.Stop(this);, the output still shows "Filling".
I did another test, I put sbFadeInStop(this); immediately after sbFadeIn.Begin(this, true); The animation stopped successfully.
I want to specifically discuss observation #2, considered that the event is fired if and only if the animation completed, the first GetcurrentState(this) should already output "Stopped". Is that correct?
Judging from the code at
https://stackoverflow.com/a/5645796/903783
you may need to use
Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)delegate() { sbFadeIn.Stop(); });
in recent C# you probably can just write
Dispatcher.Invoke(DispatcherPriority.Normal,
()=> sbFadeIn.Stop() );
however, as others have commented below, it is unlikely to be the issue since you should be already executing on the UI thread
Update:
The comment by Clemens above is also a probable path to try, that is you may need to change the FillBehavior to Stop instead of the default HoldEnd
https://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.fillbehavior(v=vs.110).aspx
Alternative is to Remove the StoryBoard (e.g. MyStoryboard.Remove(MyControl)) as shown at:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-set-a-property-after-animating-it-with-a-storyboard
There is a sample there that even shows how to remove an animation from a particular property using BeginAnimation and passing a null 2nd parameter

Custom Animation Port to Universal App from WP7 fails with SetTargetProperty

Porting custom animation code from WP7 to store app. WP7 code successfully performed a page flip animation of a border object with a bunch of text boxes on it (that was a page to be flipped.) In the below code Storyboard.SetTargetProperty does not compile complaining that it wants a string:
DoubleAnimation anima = new DoubleAnimation
{
To = pageHostBaseIndex + 1,
Duration = CalculateFractionalDuration(pageHostBaseIndex + 1)
};
Storyboard storyboard = new Storyboard();
Storyboard.SetTarget(anima, this.PageTransition);
Storyboard.SetTargetProperty(anima, new PropertyPath(PageTransition.FractionalBaseIndexProperty));
storyboard.Children.Add(anima);
storyboard.Completed += Storyboard_Completed;
storyboard.Begin();
PageTransition derives from DependencyObject, it contains a DependencyProperty called FractionalBaseIndexProperty.
I tried putting in the string "PageTransition.FractionalBaseIndexProperty" as well as constructing a PropertyPath string. I also tried "(PageTransition).(FractionalBaseIndexProperty)" these all compile but fail with the exception:
No installed components were detected.
Cannot resolve TargetProperty PageTransition.FractionalBaseIndexProperty on specified object.
at Windows.UI.Xaml.Media.Animation.Storyboard.Begin()
I also tried EnableDependentAnimation = true, and making PageTransition derive from Timeline instead of DependencyObeject but these had no effect (same error.)
The eventual animation is a little complex but I don't think it's getting that far. Seems like a Silverlight to Universal difference in objects acceptable for binding to a storyboard or in something with the path. I'll bet there's a more XAML friendly way to do this now but at this point I'm trying to minimize the port and I'd like to keep the feel of the currently animation.
Thoughts?
After hours of tinkering and searching and then reverting some of my previous tinkerings I finally got past this. This thread got me thinking:
Windows 8 - Animating custom property in code-behind.
My border objects were already declared in XAML but my storyboard, animation and PageTransition objs weren’t.
So I added the following XAML in my UserControl.Resources:
<Storyboard x:Name="storyboard">
<DoubleAnimation x:Name="anima">
</DoubleAnimation>
</Storyboard>
<local:FlipTransition x:Key="Foo"></local:FlipTransition>
I used FlipTransition because in my case the PageTransition is an abstract class (so XAML wouldn’t let me instantiate it.) FlipTranstion derives from PageTransition.
Along with that I had to re-create new storyboards and animation objects (see below) with the same names as the ones in the above XAML (I don’t know why the ones I instantiated in XAML wouldn’t work.)
I also had to set EnableDependentAnimation = true
And last, the Path in SetTargetProperty had to change to PageTransition. FractionalBaseIndex (instead of the original PageTransition.FractionalBaseIndexProperty.)
DoubleAnimation anima = new DoubleAnimation
{
To = pageHostBaseIndex + 1,
Duration = CalculateFractionalDuration(pageHostBaseIndex + 1),
EnableDependentAnimation = true
};
Storyboard storyboard = new Storyboard();
Storyboard.SetTarget(anima, this.PageTransition);
Storyboard.SetTargetProperty(anima, "PageTransition.FractionalBaseIndex");
When all these came together it worked.

MenuItem keyboard shortcuts in 'pure' MVVM?

All menus/contextmenus/toolbars I use in wpf are declared in ViewModel code pretty much like this:
MenuService.Add( new MenuItem()
{
Header = "DoStuff",
Command = new relayCommand( DoStuff, () => CanDoStuffExecute() )
// some more properties like parent item/image/...
} );
The MenuService provides a single binding point which is a hierarchical list of MenuItem and gets bound to the actual Menu's ItemsSource in xaml.
This works very well and now I'd like to add keyboard shortcuts in the same convenient way.
Ideally MenuItem would get a property of type System.Windows.Input.KeyGesture so I can simply write
Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )
which would result in the Command of the item being called upon hitting Ctrl+D in the window that owns the menu, and which would also lead to automatically display "Ctrl+D" in the menu.
However I'm lost here: I wanted to set the MenuItem.InputBindings collection via databinding but it is get-only. How can I get items into it anyway? Or is there an MVVM framework that already supports something like this? Most q&a I found on keyboard shortcuts are all about setting the shortcuts through xaml, which is of no help.
Update
Searching for 'relaycommand vs routeduicommand and 'relaycommand keygesture' etc did reveal enough information to come up with a working though hacky solution. There are definitely other and better ways out there, but at the moment this is ultra low priority for me and does the job perfectly. I added two properties to the MenuItem class like this:
//Upon setting a Gesture, the original command is replaced with a RoutedCommand
//since that automatically gives us proper display of the keyboard shortcut.
//The RoutedCommand simply calls back into the original Command.
//It also sets the CommandBinding property, which still has to be added to the
//CommandBindingCollection of either the bound control or one of it ancestors
public InputGesture Gesture
{
set
{
var origCmd = Command;
if( origCmd == null )
return;
var routedCmd = new RoutedCommand( Header,
typeof( System.Windows.Controls.MenuItem ),
new InputGestureCollection { value } );
CommandBinding = new CommandBinding( routedCmd,
( sender, args ) => origCmd.Execute( args.Parameter ),
( sender, args ) => { args.CanExecute = origCmd.CanExecute( args.Parameter ); } );
Command = routedCmd;
}
}
//CommandBinding created when setting Gesture
public CommandBinding CommandBinding { get; private set; }
So this gives the functionality I asked for originally (ie adding keyboard shortcuts in code where they are easily configurable etc). All that is left is to register the commandbindings. At the moment this is done simply by adding all of them to Application.Current.MainWindow.CommandBindings.
This doesn't actually qualify as an 'answer' (I'm not able to add a comment evidently) - but I'd suggest that what you're doing, is not the intended method in WPF. You're doing this the Windows Forms way (and as in many other toolkits) - defining your UX in code. You got as far as you did, but now you've run into a brick wall: the key gestures are purely UX, definitely not to be specified in code-behind. The appearance (as a function of the view-model), and the user's interaction with it (ways of making a given command happen) are for the XAML definition.
Property values, and Commands are for your view-model, so that you can reuse this view-model for other views, and also easily create unit-tests for it. How would implementing your keyboard shortcuts in the view-model help the testability? And for use in other views, one could argue that the actual shortcuts might not apply to a new view, so that is not where those belong. You may have other reasons of course - but I'd suggest you might consider just defining these in XAML.
-Added, in response to your comment-
You're quite right - and I've seen some rather large WPF UX projects that tried hard to avoid any code-and wound up unnecessarily obtuse. I try to just use whichever approach yields a working result, and is as simple as I can get it.
Here is a sample snippet that simply creates the MenuItem..
<Menu x:Name="miMain" DockPanel.Dock="Top">
<MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>
That creates the menu. Here, MyGreatCommand is an ICommand, and is simply a property on the view-model. I generally place that within a DockPanel, to handle the StatusBar, etc.
To assign the key gesture..
<Window.InputBindings>
<KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>
However, since you mentioned that you've already searched for answers and found only XAML - I assume you've already tried this approach. I have used RoutedUICommands instead of user-defined ICommands, to get that nice right-aligned key-gesture in the header text, but I haven't found how to do both. If you insist upon creating the commands and key-gestures all in code, you may have to create RoutedUICommands.
Why are you wanting to set the key-gestures in other than your XAML?
If you want some menu-items to appear only when certain states hold sway within your view-model, then you can bind the Visibility property of a menu-item (which can contain other menu-items) to Collapsed or Visible.

Proper way to remove a Trigger

I am working with a form where some ComboBoxes can be created and removed programmatically.
When they are created, some triggers which target them are created and applied to a button:
Dictionary<ComboBox, DataTrigger> triggers = new Dictionary<ComboBox, DataTrigger>();
private void CreateTrigger(ComboBox box)
{
Style s = new Style(typeof(Button), MyButton.Style);
foreach(TriggerBase aTrigger in MyButton.Style.Triggers)
s.Triggers.Add(aTrigger);
DataTrigger t = new DataTrigger
{
Binding = new Binding("SelectedItem") { Source = box },
Value = null
};
t.Setters.Add(new Setter(Button.IsEnabledProperty, false));
s.Triggers.Add(t);
triggers.Add(box, t);
MyButton.Style = s;
}
So far so good*. . . the problem is, what to do when the ComboBox is removed from the window. I need to remove the trigger from the button's Style, since I no longer want the ComboBox to influence its behavior. I tried the most obvious option:
private void RemoveTrigger(ComboBox box)
{
Style s = new Style(typeof(Button), MyButton.Style);
foreach(TriggerBase aTrigger in MyButton.Style)
if(aTrigger != triggers[box]) s.Triggers.Add(aTrigger);
triggers.Remove(box);
MyButton.Style = s;
}
But this does not seem to do the job - if the trigger is removed while it is active, then the button stays disabled.
I had assumed that the button would re-evaluate its Style whenever it is given a new one. that seems to be happening when the trigger is added, but not when it's being removed - what am I missing here?
EDIT: Changed code for adding/removing triggers as per the advice in H.B.'s comment. However, the problem in question remains.
EDIT 2: *Maybe not so far so good after all - I went on to try adding an additional ComboBox (and trigger) and discovered that adding a second trigger seems to break the first one. Using this code, only the most recently added trigger works. Should I be perhaps thinking of a FrameworkElement's triggers as a write-once collection and finding a different way to achieve this kind of behavior?
So you create a style BasedOn the style of MyButton (that is what this contructor does), then you add the trigger and change the reference of your button's style to your new style. In the removal you create a new style, again based on the style currently referenced by your button, remove a trigger from its trigger collection which will not do anything as the collection is empty and then reassign this style again.
Nope, this of course won't work.
Edit: Create a base-style as a readonly reference, then when those dynamic triggers are to be added or removed create a new style based on your reference and recreate all triggers while iterating over your trigger-collection.
It's been a long time since this question was asked, but I figured I'd at least post how I resolved the issue for the sake of sharing:
I never did find a way to remove triggers that worked reliably. So instead, I added a property to my View which indicated how all the triggers would have evaluated, if they had existed, and hooked a DataTrigger up to this property.
public bool TriggerPoseur { get; set; } // Actually notifies when it changes
In place of adding and removing triggers, created an handlers to watch the properties that the triggers would have watched:
public void ComboBoxDataContext_SelectedItemChanged(object sender, PropertyChangedEventArgs e) { //update TriggerPoseur }
This sidesteps all the hassle with creating and removing triggers. Instead there's one trigger, and adding and removing event handlers works just fine.
(Hacky, yes.)

Categories