Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I trigger a closing animation for a WPF ContextMenu?

Tags:

c#

wpf

Does anyone know if it is possible to trigger an animation when a WPF ContextMenu closes?

I have code that triggers an animation when the ContextMenu is opened. The animation makes the context menu fade into view. I also want an animation when the ContextMenu is closed that makes it fade out.

The code that starts the opened fade-in animation looks something like this:

        var animation = new DoubleAnimation();
        animation.From = 0;
        animation.To = 1;
        animation.Duration = TimeSpan.FromSeconds(0.2);
        animation.Freeze();

        menu.BeginAnimation(ContextMenu.OpacityProperty, animation);

The fade-in animation also runs on sub-menu items.

Note that I also want to run other animations besides fade in and fade out. Eg I want the context menu to scale up from nothing so that it sort of 'bounces' into view.

like image 964
Ashley Davis Avatar asked Jun 04 '10 09:06

Ashley Davis


1 Answers

Other than Popup.PopupAnimation, there is no hook within ContextMenu or Popup to allow you to delay the dismantling of the ContextMenu, destruction of the window, etc, long enough to show your animation. This leaves you with several choices:

  1. You can use Popup.PopupAnimation to delay the popup closing, then replace its animation with your own,
  2. You can present the ContextMenu in your own Popup in ContextMenuClosing, play your animation, and remove it
  3. You can implement your own code to handle right-click, Shift-F10, etc to create the Popup and display the ContextMenu within it

Using Popup.PopupAnimation

In the ContextMenuOpened event, find the popup and set Popup.PopupAnimation to any animation, then monitor Popup.IsOpen and when the IsOpen property goes false, use a Dispatcher callback to replace the scheduled animation with your own. Your animation can reuse the TranslateTransform created by the Popup class or it can add its own transform.

This techinique is simple and compatible, but the drawback is that you have no control over the duration of the (fixed) interval between the time the popup decides to close and the time everything is dismantled. It seems to be about 1/6 second, so if you can live with that this is probably the way to go.

Using your own Popup to display the ContextMenu during the close animation

By the time you get ContextMenuClosing the Popup that was displaying the menu is already gone, but you can temporarily create a new one.

To avoid flicker, this must be done at DisplatchPriority.Render or higher. Also, the new Popup must be at exactly the same position and size as the one created during menu popup. These coordinates can be recorded immediately after the ContextMenuOpened event. You'll have to do this in a Dispatcher callback because the coordinates aren't actually available during the ContextMenuOpened event.

So the procedure is as follows:

  • On ContextMenuOpened, do a Dispatcher.BeginInvoke of an Action that records the popup position and size.
  • On ContextMenuClosed, do a Dispatcher.BeginInvoke of an Action that constructs a Popup at that position and size whose Child is the ContextMenu, sets its IsOpen true, starts the animation
  • When the animation ends (this can be done using a timer), set the Popup's IsOpen false and clear its Child property
  • Don't forget to make sure the ContextMenu's DataContext is set correctly during the animation time so it will display the same data as it was before the ContextMenu was closed.

Implementing your own ContextMenu handling code

If you mark your ContextMenuEventArgs.Handled true in the ContextMenuOpened event, the ContextMenu code doesn't actually do anything, allowing you to present the ContextMenu yourself. To do this:

  1. Construct a Popup, compute appropriate placement (this is definitely non-trivial!), and add the ContextMenu as its Child
  2. Set the Popup.IsOpen true and start your opening animation
  3. When it is time for the ContextMenu to close, start your closing animation, then set Popup.IsOpen false and clear its Child property

The tricky part of this is reliably deciding when to close the ContextMenu (step 3) based on user actions. I don't know of any way to reuse NET Framework's built-in mechanisms for this and the rules for when the ContextMenu should close are quite complex.

like image 65
Ray Burns Avatar answered Oct 06 '22 05:10

Ray Burns