WPF animations inside user control - wpf

I have a user control that acts like a progress bar and animates the width of a rectangle as a response to an event. Someone arises the event with certain % and the rectangle width animates from its actual width to the % of the actualWidth of the user control.
IF I try to set the new width I get the "The calling thread cannot access this object because a different thread owns it." So I use the Dispatcher.Invoke and it runs nicely.
The problem appears if I try to animate the width change instead of just setting it. Then I get the different thread owns it error event when using the dispatcher.
So. This piece of code works nicely:
bar.Dispatcher.Invoke((Action)delegate { bar.Width = myWidth; });
But this piece of code does not:
DoubleAnimation widthAnimation = new DoubleAnimation();
widthAnimation.From = bar.ActualWidth;
widthAnimation.To = myWidth;
widthAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
widthAnimation.RepeatBehavior = new RepeatBehavior(1);
bar.Dispatcher.Invoke( (Action)delegate {
bar.BeginAnimation(Rectangle.WidthProperty, widthAnimation);
});
So.. how am I suposed to run an animation on a user control like this one??
Thanks in advance !!!

The animation should also be created in the UI thread:
bar.Dispatcher.Invoke((Action)delegate
{
var widthAnimation = new DoubleAnimation
{
From = bar.ActualWidth,
To = myWidth,
Duration = TimeSpan.FromMilliseconds(500)
};
bar.BeginAnimation(Rectangle.WidthProperty, widthAnimation);
});

Related

WPF custom shader effect property binding for animation

I'm implementing transitions in a WPF application.
First I "save" my 2 FrameworkElement in 2 ImageBrush.
Then I set the Input & Back (Brush) properties of my shader Effect with them.
CustomEffect s = new CustomEffect();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)SourceFrameWorkElement.ActualWidth, (int)SourceFrameWorkElement.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(SourceFrameWorkElement);
ImageBrush ib = new ImageBrush(rtb);
s.Input = ib;
rtb.Render(TargetFrameWorkElement);
ib.ImageSource = rtb;
s.Back = ib;
SourceFrameWorkElement.Effect = s;
Now that all is set up, I want to animate the Time property of my shader, and i've tried this:
DoubleAnimation refDoubleAnimation = new DoubleAnimation(0.0, 1.0, Duration);
Storyboard.SetTarget(refDoubleAnimation, SourceFrameWorkElement);
Storyboard.SetTargetProperty(refDoubleAnimation, new PropertyPath("(Effect).(CustomEffect.Time)");
refStoryboard.Children.Add(refDoubleAnimation);
refStoryboard.Completed += new EventHandler(OnStoryboardCompleted);
refStoryboard.Begin(SourceFrameWorkElement, true);
and i get an InvalidOperationException on the begin method with this message:
"Cannot resolve all property references in the property path '(Effect).(CustomEffect.Time)'.
Verify that applicable objects supports the properties."
But when I use a built in Effect like BlurEffect, it works....
Can someone tell me where i'm wrong ?
Edit:
I've also tried
SourceElement.Effect.BeginAnimation(SlideInEffect.TimeProperty, refDoubleAnimation)
instead of using the storyboard, I don't get an exception but the second image pop instantly and the animation is not playing
The solution was to use BeginAnimation ^^
In fact, I had the second image with opacity to 1, and the animation was playing behind
(i checked if the time elapsed to get in my OnAnimationCompleted eventHandler matched with the transition Duration)
so i've created a second animation on the TargetElement opacity with 2 DiscreteDoubleKeyFrames to do the trick and now it works ^^
Maybe the Storyboard thing could work if i add the namespace in the PropertyPath but i have no time to test it so give it a try if you want, and update the post ^^.

WPF animation problem when using Storyboard from code

I'm working on a 3D carousel of flat, square tiles that will contain information. I'm working on animating this carousel to rotate when a person presses Next and Previous buttons.
I've gotten it to work by using BeginAnimation on the Rotation property of the RotateTransform3D I applied to the carousel, but I can't seem to make a Storyboard version of the same animation work. The reason I need the Storyboard version is for the HandOffBehavior.Compose parameter because without it, multiple clicks of my next and previous buttons results in a misaligned carousel.
Here is the code for the Storyboard:
RotateTransform3D tempTransform = (RotateTransform3D)wheel.Transform;
AxisAngleRotation3D rotation = (AxisAngleRotation3D)tempTransform.Rotation;
Storyboard storyboard = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.By = defaultAngle;
animation.Duration = TimeSpan.FromSeconds(.5);
Storyboard.SetTarget(animation, rotation);
Storyboard.SetTargetProperty(animation, new PropertyPath("Angle"));
storyboard.Children.Add(animation);
storyboard.Duration = animation.Duration;
storyboard.Begin(new FrameworkContentElement(), HandoffBehavior.Compose);
For some reason, this code results in absolutely nothing. I followed the examples I had to the letter, so I am quite frustrated. Any help is greatly appreciated. I am also completely open to using BeginAnimation if I can replicate HandOffBehavior.Compose.
My experience comes from 2D animation, but I guess the problem is the same.
For some stupid reason (probably relating to an unhealthy focus on XAML), Storyboards can only animate Freezable objects by looking them up by name. (See example in Storyboards Overview.) Thus although you provide a reference to your 'rotation' object when you call Storyboard.SetTarget(animation, rotation), the Storyboard only wants to remember and use a name, which it does not have.
The solution is:
Create a naming scope around the element that will govern the transform.
Call RegisterName() for each Freezable object being animated.
Pass the element to Storyboard.Begin()
Which would make your code look something like this (not tested):
FrameworkContentElement element = new FrameworkContentElement();
NameScope.SetNameScope(element, new NameScope());
RotateTransform3D tempTransform = (RotateTransform3D)wheel.Transform;
AxisAngleRotation3D rotation = (AxisAngleRotation3D)tempTransform.Rotation;
element.RegisterName("rotation", rotation);
Storyboard storyboard = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.By = defaultAngle;
animation.Duration = TimeSpan.FromSeconds(.5);
Storyboard.SetTarget(animation, rotation);
Storyboard.SetTargetProperty(animation, new PropertyPath("Angle"));
storyboard.Children.Add(animation);
storyboard.Duration = animation.Duration;
storyboard.Begin(element, HandoffBehavior.Compose);
None of this is necessary in XAML because your objects are automatically registered.
EDIT: But then I worked out that you can simplify things by leaving out the Storyboard altogether:
var T = new TranslateTransform(40, 0);
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0);
DoubleAnimation anim = new DoubleAnimation(30, duration);
T.BeginAnimation(TranslateTransform.YProperty, anim);

Is it possible for a WPF control to have an ActualWidth and ActualHeight if it has never been rendered?

I need a Viewport3D for the sole purpose of doing geometric calculations using Petzold.Media3D.ViewportInfo. I would prefer not to have to place it in a Window or otherwise render it.
I'm attempting to accomplish this by instantiating a Viewport3D and setting a few properties using the following C# method:
private Viewport3D CreateViewport(MainSettings settings)
{
var cameraPosition = new Point3D(0, 0, settings.CameraHeight);
var cameraLookDirection = new Vector3D(0, 0, -1);
var cameraUpDirection = new Vector3D(0, 1, 0);
var camera = new PerspectiveCamera
{
Position = cameraPosition,
LookDirection = cameraLookDirection,
UpDirection = cameraUpDirection
};
var viewport = new Viewport3D
{
Camera = camera,
Width = settings.ViewportWidth,
Height = settings.ViewportHeight
};
return viewport;
}
Later, I'm attempting to use this viewport to convert the mouse location to a 3D location using this method:
public Point3D? Point2dToPoint3d(Point point)
{
var range = new LineRange();
var isValid = ViewportInfo.Point2DtoPoint3D(_viewport, point, out range);
if (isValid)
return range.PointFromZ(0);
else
return null;
}
Unfortunately, it's not working. I think the reason is that the ActualWidth and ActualHeight of the viewport are both zero (and these are read-only properties, so I can't set them manually). (Note: I have tested the exact same method with an actual rendered Viewport3D, and it worked fine, so I know the issue is not with my converter method.)
Any idea how I can get WPF to assign the ActualWidth and ActualHeight of a control based on the Width and Height settings?
I tried setting the HorizontalAlignment and VerticalAlignment to Left and Top, respectively, and I also messed with the MinWidth and MinHeight, but none of these properties had any effect on the ActualWidth or ActualHeight.
From MSDN topic for the ActualWidth property:
This property is a calculated value based on other width inputs, and the layout system. The value is set by the layout system itself, based on an actual rendering pass, and may therefore lag slightly behind the set value of properties such as Width that are the basis of the input change.
So, this sounds like a rendering pass is necessary for the property to be set. However, you could try to call Measure(Size) and then Arrange(Rect) to simulate the layout process. Maybe this is already sufficient.

WPF: How to apply a change to an Opacity/Background immediately? (WPF analog to WinForms Control.Update() method?)

I have a WPF app, upon clicking a button, the app goes into a calculation that can take 4-10 seconds. I'd like to update the opacity of the background and show a progress bar, during that operation.
To do that, I use this code:
this.Cursor = System.Windows.Input.Cursors.Wait;
// grey-out the main window
SolidColorBrush brush1 = new SolidColorBrush(Colors.Black);
brush1.Opacity = 0.65;
b1 = LogicalTreeHelper.FindLogicalNode(this, "border1") as Border;
b1.Opacity = 0.7;
b1.Background = brush1;
// long running computation happens here ....
// show a modal dialog to confirm results here
// restore background and opacity here.
When I run the code, the background and opacity doesn't change until the modal dialog appears. How can I get those visual changes to happen right now, before the calculation begins? In Windows Forms there was an Update() method on each control, that did this as necessary, as I recall. What's the WPF analog?
What if you would do long running computation in the background thread? Once they are done dispatch results back to UI thread...
Honestly, I suspect there is nothing else there, that can solve your problem. Maybe nested pumping will do the trick, but I really doubt it.
Just in case this reference is helpful: Build More Responsive Apps With The Dispatcher
Use the DoEvents() code as shown here:
http://blogs.microsoft.co.il/blogs/tamir/archive/2007/08/21/How-to-DoEvents-in-WPF_3F00_.aspx
My actual code:
private void GreyOverlay()
{
// make the overlay window visible - the effect is to grey out the display
if (_greyOverlay == null)
_greyOverlay = LogicalTreeHelper.FindLogicalNode(this, "overlay") as System.Windows.Shapes.Rectangle;
if (_greyOverlay != null)
{
_greyOverlay.Visibility = Visibility.Visible;
DoEvents();
}
}
private void DoEvents()
{
// Allow UI to Update...
DispatcherFrame f = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new Action<object>((arg)=> {
DispatcherFrame fr = arg as DispatcherFrame;
fr.Continue= false;
}), f);
Dispatcher.PushFrame(f);
}

How to show a loading graphic/animation when wpf data binding is taking place

I have a WPF user control that contains a DataGrid. I'm binding an ObservableCollection of view models to it. Each view model has another collection of view models that I'm using to bind another DataGrid to. So the effect is a DataGrid with a nested DataGrid contained in the row details template.
Normally the binding is quite quick, but sometimes when there's a lot of data it can hang the UI while the binding/drawing is taking place.
Is there a way where I can either show a loading animation or progress bar while the binding/drawing is in progress?
There's probably a more formal, or at least simpler solution, but you could use a modal popup window that is shown in a worker thread and is closed asynchronously when your is grid done loading:
Window waitWindow = new Window { Height = 100, Width = 200, WindowStartupLocation = WindowStartupLocation.CenterScreen, WindowStyle = WindowStyle.None };
waitWindow.Content = new TextBlock { Text = "Please Wait", FontSize = 30, FontWeight = FontWeights.Bold, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Dispatcher.BeginInvoke(new Action(delegate { waitWindow.ShowDialog(); }));
DataLoader dataLoader = new DataLoader(); // I made this class up
dataLoader.DataLoaded += delegate
{
Dispatcher.BeginInvoke(new Action(delegate() { waitWindow.Close(); }));
};
dataLoader.LoadData();
};
worker.RunWorkerAsync();
You can replace the TextBlock with something pretty like a loading bar, and you could make the code re-usable by parameterizing the object that handles the loading of the grid(s) and passing it in to a commonly used method.
I hope that works for you.
I had the same problem and this is how I solved it.
I discovered that DataGrid will only start creating controls when it displays the grid. In my case this was the time consuming process. After some tracing I found that creating the controls happens during measuring !
My solution is to override MeasureOverride and put the wait cursor around the base class call. I encapsulated my wait cursor setting in a class. So the code looks like this.
protected override Size MeasureOverride(Size availableSize)
{
using (new DisposableWaitCursor(this))
{
return base.MeasureOverride(availableSize);
}
}

Resources