简体   繁体   中英

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

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.

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.

您无法使用ContextMenuClosing事件

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM