vdhant

Hi guys

When i change the width of my window with an animation, the left hand size stays on the given x coordinate while the right side zooms out until the width of the window reaches its correct size. How do i make it so that the reverse is true. In that, the right side stays still while the left shoots out.

Thanks

Anthony



Re: Windows Presentation Foundation (WPF) Width change animation

vdhant

Anyone got any ideas on this one

Thanks

Anthony





Re: Windows Presentation Foundation (WPF) Width change animation

Dr. WPF

There appears to be a bug that prevents this from being possible in a smooth way. It would be great if someone at Microsoft could repro this and confirm.

The following should work, but does not. (Note, I only tested on 3.0, so if you're running 3.5, you can give it a try. I don't currently have access to my 3.5 machine.)

Code Snippet

public partial class Window1 : Window

{

static Window1()

{

WidthProperty.OverrideMetadata(typeof(Window1),

new FrameworkPropertyMetadata(

new PropertyChangedCallback(OnWidthChanged)));

}

public Window1()

{

InitializeComponent();

}

private void AnimateWidthToTheLeft(object sender, RoutedEventArgs e)

{

_right = Left + ActualWidth;

DoubleAnimation da = new DoubleAnimation(ActualWidth + 100,

new Duration(TimeSpan.FromMilliseconds(300)));

da.Completed += new EventHandler(WidthAnimationCompleted);

BeginAnimation(Window.WidthProperty, da);

}

private static void OnWidthChanged(DependencyObject d,

DependencyPropertyChangedEventArgs e)

{

Window1 window = d as Window1;

if (!double.IsNaN(window._right))

{

window.Left = window._right - (double)e.NewValue;

Debug.WriteLine("Width: " + window.Width

+ "; ActualWidth: " + window.ActualWidth);

}

}

private void WidthAnimationCompleted(object sender, EventArgs e)

{

(sender as AnimationClock).Completed

-= new EventHandler(WidthAnimationCompleted);

_right = double.NaN;

}

private double _right = double.NaN;

}

This code actually translates the window to the left but does not resize it. If you watch the debug output, you'll see that the Width property and the ActualWidth property diverge during the animation. The Width property grows by 100 pixels by ActualWidth stays constant. I'm guessing that the underlying call to SetWindowPos() is passing the incorrect values. Simultaneously changing Left and Width is probably throwing off the logic.

You can accomplish your goal in an unsmooth way (with the window jumping) by applying a Width animation and using the animation clock's TimeInvalidated event to update the Left property.

There are other options, such as using a large transparent window that is completely chromeless and expanding the window bounds prior to starting your animation (which would be an animation into the unfilled space), but I imagine this will also include a jump when the initial window position is changed.






Re: Windows Presentation Foundation (WPF) Width change animation

Dr. WPF

Here's a workaround for the aforementioned bug. Note that this approach is animating the Left property and using it's property changed callback to update the Width. This is the inverse of the earlier approach. The framework handles it much better.

As far as perf is concerned, this is probably as good as it gets when it comes to animating the size of an hwnd. Any such animation invalidates the entire client rect, which is why you see flashing and visual artifacts during the animation.

Code Snippet

public partial class Window1 : Window

{

static Window1()

{

LeftProperty.OverrideMetadata(typeof(Window1),

new FrameworkPropertyMetadata(

new PropertyChangedCallback(OnLeftChanged)));

}

public Window1()

{

InitializeComponent();

}

private void AnimateWidthToTheLeft(object sender, RoutedEventArgs e)

{

_right = Left + ActualWidth;

DoubleAnimation da = new DoubleAnimation(Left, Left - 100,

new Duration(TimeSpan.FromMilliseconds(300)), FillBehavior.HoldEnd);

da.Completed += new EventHandler(LeftAnimationCompleted);

BeginAnimation(Window.LeftProperty, da);

}

private static void OnLeftChanged(DependencyObject d,

DependencyPropertyChangedEventArgs e)

{

Window1 window = d as Window1;

if (!double.IsNaN(window._right))

{

window.Width = window._right - window.Left;

Debug.WriteLine("Width: " + window.Width

+ "; ActualWidth: " + window.ActualWidth);

}

}

private void LeftAnimationCompleted(object sender, EventArgs e)

{

AnimationClock clock = sender as AnimationClock;

clock.Completed -= new EventHandler(LeftAnimationCompleted);

_right = double.NaN;

Left = Left;

clock.Controller.Stop();

}

private double _right = double.NaN;

}






Re: Windows Presentation Foundation (WPF) Width change animation

vdhant

Thanks for this...

Unfortunately i am not able to check it at the moment but as soon as i do i will let you know.

Anthony





Re: Windows Presentation Foundation (WPF) Width change animation

Yi-Lun Luo - MSFT

Hello Dr WPF, your first approach doesn¡¯t work is not because there¡¯s a bug in the framework. You¡¯ve overridden Width property¡¯s metadata. However, the Window class relies on some internal implementations in its original OnWidthChanged callback to update width (basically interoperate with Win32), and by overriding the metadata, you¡¯ve skipped the internal OnWidthChanged callback. That¡¯s why width seems not to change.

Generally speaking, it is not recommended to override the metadata of properties defined in the framework. You must be very careful that you don¡¯t skip any important internal implementations. To add a ValueChanged callback, you can use DependencyPropertyDescriptor:

//Do this in the public constructor, not the static constructor.

DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Window.WidthProperty, typeof(Window));

dpd.AddValueChanged(this, new EventHandler(OnWidthChanged));

By doing so, you won¡¯t skip any internal implementations. Of course, you also need to change your callback a little:

private static void OnWidthChanged(object d, EventArgs e)

{

Window1 window = d as Window1;

if (!double.IsNaN(window._right))

{

window.Left = window._right - window.ActualWidth;

Debug.WriteLine("Width: " + window.Width + "; ActualWidth: " + window.ActualWidth);

}

}

And, I think an easier solution will be use two animations in a single Storyboard. You can even do it in XAML:

<Window.Resources>

<Storyboard x:Key="sb">

<DoubleAnimation Storyboard.TargetName="win" Storyboard.TargetProperty="Width" From="300" To="500"/>

<DoubleAnimation Storyboard.TargetName="win" Storyboard.TargetProperty="Left" From="500" To="300"/>

</Storyboard>

</Window.Resources>

<Window.Triggers>

<EventTrigger RoutedEvent="Window.Loaded">

<BeginStoryboard Storyboard="{StaticResource sb}"/>

</EventTrigger>

</Window.Triggers>

Here ¡°win¡± is the name I gave to the Window.






Re: Windows Presentation Foundation (WPF) Width change animation

Dr. WPF

Thanks for checking this out and defending the platform, Yi-Lun! Good call on the metadata override. Based on the symptoms, it should have occurred to me to reflect the Window class to see what I was screwing up. That's what I get for coding at 3 AM.

The DPD approach is much safer, but it's also more jittery. This implies that it involves two SetWindowPos() calls (one for width and one for left) rather than one. By using metadata override on the Left property (my second approach), it was very smooth, which led me to believe the width and left properties were probably updated within the same dispatcher operation and a single SetWindowPos() call was happening in a separate dispatcher operation (or at least later in the current operation).

The problem with a storyboard solution for this type of animation is that you will almost always have to build up the animations in code. You typically won't know the current Left and Width properties of your window (unless it's docked). Plus, using a storyboard, I see the Window periodically jump horizontally during the animation, which again tells me the independent animations are causing independent calls to SetWindowPos().

I guess the only way to ensure no jitters would be to provide a single Rect property that could be used to set the window bounds. This feature (the ability to set the left, top, width, and height properties atomically) is something I requested about 4 years ago, but that's okay... I'm not bitter.

However, if you did want to use a storyboard approach, I would make the argument that you should use a couple of "By" animations:

Code Snippet

<Storyboard x:Key="sb" Storyboard.TargetName="win">

<DoubleAnimation Storyboard.TargetProperty="Width" By="200" />

<DoubleAnimation Storyboard.TargetProperty="Left" By="-200" />

</Storyboard>

This way, there's only a single property on each animation to update and you don't need to know the current values. You could also use a binding on the By property (to prevent the animation from being frozen), and then update it easily in code prior to starting the storyboard. That's almost as good as creating the animations in code.





Re: Windows Presentation Foundation (WPF) Width change animation

vdhant

Firstly i second Dr. WPF's suggestion about the "By" on the animations. I am sick of writing code like this:

Code Snippet

<Storyboard x:Key="CompressMainOptions">

<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="brdMainOptions" Storyboard.TargetProperty="(FrameworkElement.Width)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="{Binding ElementName=brdMainOptions, Path=Width}"/>

<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="{Binding ElementName=brdMainOptions, Path=MinWidth}"/>

</< FONT>DoubleAnimationUsingKeyFrames>

</< FONT>Storyboard>

<Storyboard x:Key="ExpandMainOptions">

<DoubleAnimationUsingKeyFrames BeginTime="00:00:00.4" Storyboard.TargetName="brdMainOptions" Storyboard.TargetProperty="(FrameworkElement.Width)">

<SplineDoubleKeyFrame KeyTime="00:00:00" Value="{Binding ElementName=brdMainOptions, Path=Width}"/>

<SplineDoubleKeyFrame KeyTime="00:00:00.1500000" Value="{Binding ElementName=brdMainOptions, Path=MaxWidth}" />

</< FONT>DoubleAnimationUsingKeyFrames>

</< FONT>Storyboard>

I know using the Min and Max is a bit of a hack but it is the only real way that i have been able to find a practical way of doing "relative" animations. I find that this works really well when the expand is called (i.e. like on a mouse over) but then before the expand finishes the compress is called (i.e. like when the move their mouse off quickly).

On the topic at hand i put together the following after what everyone has said and your right the animation is really doggy. I am luck that in my case that surrounding my app i have the window style set to none so the animation looked really good when i had a test window on it. But as soon as i applied the code to my real app, it still looked better than it did with the window style set but you get a heap of artefacts.

It would good to see this fixed up in the next version of WPF.

If anyone has any other suggestions or comments let me know.

Thanks

Anthony

Code Snippet

public partial class Window1 : Window

{

private static void OnWidthChanged(object d, EventArgs e)

{

Window1 window = d as Window1;

if (double.IsNaN(window._right))

window._right = window.Left + window.ActualWidth - 2;

if (!double.IsNaN(window._right))

{

window.Left = window._right - window.ActualWidth;

Debug.WriteLine("Width: " + window.Width + "; ActualWidth: " + window.ActualWidth);

}

}

public Window1()

{

InitializeComponent();

DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(System.Windows.FrameworkElement.WidthProperty, typeof(Window));

dpd.AddValueChanged(this, new EventHandler(OnWidthChanged));

}

private double _right = double.NaN;

}