Search code examples
c#vb.netwinformsanimationcontextmenustrip

How to add custom animations to a ContextMenuStrip?


Is there a way to create an animation when a ContextMenuStrip is opened, like in the this image?

enter image description here

this animation is for WhatsApp right click.I have no idea how to do this.


Solution

  • You can use the AnimateWindow function with ToolStrips, which includes the ContextMenuStrip class, since these all have handles.
    For example, you can reproduce the animation shown in the OP handling the MouseDown event of a Control:

    Private Sub SomeControl_MouseDown(sender As Object, e As MouseEventArgs) Handles SomeControl.MouseDown
        If e.Button = MouseButtons.Right Then
            Dim ctrl = DirectCast(sender, Control)
            Dim cms = ContextMenuStrip1
            cms.Location = MousePosition
            AnimateWindow(cms.Handle, 250, AW_VER_POSITIVE Or AW_HOR_POSITIVE)
            cms.Show(ctrl, ctrl.PointToClient(MousePosition))
        End If
    End Sub
    

    This implies that you have to handle the MouseDown event of each Control that needs an animated ContextMenuStrip. If it's just one, it may be acceptable. Or a bunch o Controls that can share the same MouseDown event.
    But you cannot apply the animation to all Controls in a Form, setting the ContextMenuStrip to the Form class, for example.

    It's probably better to build a Custom Control, inheriting ContextMenuStrip.
    You can then add some Properties that allow to configure the ContextMenuStrip behavior, adding some animations that are supported.

    I'm adding a few:

    • OpenDownwards: as in the OP's sample image
    • OpenUpwards: the opposite
    • Expand: expand the menu from its center

    Slide (self-explanatory):

    • SlideToRight, SlideToLeft
    • SlideDownwards, SlideUpwards

    New Public Properties:

    • AnimationTime: the speed of the animation, in milliseconds
    • AnimationType: the type of animation. ComboBox selector
    • AnimateInDesigner: animate the ContextMenuStrip in the Form Designer. Makes it easier to configure it, since the animation is shown at design-time.
      Off by default, set it to True to turn it on. You may want to turn it back off after the control is configured.

    Imports System.ComponentModel
    Imports System.Runtime.InteropServices
    Imports System.Windows.Forms
    
    <DesignerCategory("code")>
    Public Class ContextMenuStripAnimated
        Inherits ContextMenuStrip
    
        ' Don't remove
        Public Sub New()
        End Sub
    
        Public Sub New(container As IContainer)
            Me.New()
            If container Is Nothing Then
                Throw New ArgumentNullException("container is null")
            End If
            container.Add(Me)
        End Sub
    
        Protected Overrides Sub OnOpening(e As CancelEventArgs)
            If Not DesignMode OrElse AnimateInDesigner Then
                Dim result = AnimateWindow(Handle, AnimationTime, AnimationType)
            End If
            MyBase.OnOpening(e)
        End Sub
    
        <DefaultValue(False)>
        Public Property AnimateInDesigner As Boolean = False
    
        <DefaultValue(AnimationMode.OpenDownwards)>
        Public Property AnimationType As AnimationMode = AnimationMode.OpenDownwards
    
        <DefaultValue(250)>
        Public Property AnimationTime As UInteger = 250
    
        <Flags()>
        Public Enum AnimationMode As UInteger
            CloseCentered = AW_CENTER Or AW_HIDE
            CloseDownwards = AW_VER_POSITIVE Or AW_HOR_POSITIVE Or AW_HIDE
            CloseUpwards = AW_VER_NEGATIVE Or AW_HOR_NEGATIVE Or AW_HIDE
            OpenCentered = AW_CENTER
            OpenDownwards = AW_HOR_POSITIVE Or AW_VER_POSITIVE
            OpenUpwards = AW_HOR_NEGATIVE Or AW_VER_NEGATIVE
            SlideToRight = AW_SLIDE Or AW_HOR_POSITIVE
            SlideToLeft = AW_SLIDE Or AW_HOR_NEGATIVE
            SlideDownwards = AW_SLIDE Or AW_VER_POSITIVE
            SlideUpwards = AW_SLIDE Or AW_VER_NEGATIVE
            Hide = AW_HIDE
        End Enum
    
    #Region "NativeMethods"
    
        Private Const AW_HOR_POSITIVE As UInteger = &H1
        Private Const AW_HOR_NEGATIVE As UInteger = &H2
        Private Const AW_VER_POSITIVE As UInteger = &H4
        Private Const AW_VER_NEGATIVE As UInteger = &H8
        Private Const AW_CENTER As UInteger = &H10
        Private Const AW_HIDE As UInteger = &H10000
        Private Const AW_ACTIVATE As UInteger = &H20000
        Private Const AW_SLIDE As UInteger = &H40000
        Private Const AW_BLEND As UInteger = &H80000
    
        <DllImport("user32.dll", SetLastError:=true)>
        Private Shared Function AnimateWindow(hwnd As IntPtr, time As UInteger, flags As AnimationMode) As Boolean
        End Function
    
    #End Region
    End Class
    

    To add this new Control to the ToolBox:

    • Add a Class to the Project, name it ContextMenuStripAnimated
    • Copy all the code shown here, including the Imports directives
    • Paste it inside the new Class file, replacing everything that's in there
    • Build the Project
    • Find the new Control in the ToolBox and add it to a Form

    C# version, just in case:

    using System;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    [DesignerCategory("code")]
    public class ContextMenuStripAnimated : ContextMenuStrip
    {
        public ContextMenuStripAnimated() { }
    
        public ContextMenuStripAnimated(IContainer container) : this()
        {
            if (container == null) {
                throw new ArgumentNullException("container is null");
            }
            container.Add(this);
        }
    
        protected override void OnOpening(CancelEventArgs e)
        {
            if (!DesignMode || AnimateInDesigner) {
                var result = AnimateWindow(Handle, AnimationTime, AnimationType);
            }
            base.OnOpening(e);
        }
    
        [DefaultValue(false)]
        public bool AnimateInDesigner { get; set; } = false;
    
        [DefaultValue(250)]
        public uint AnimationTime { get; set; } = 250;
    
        [DefaultValue(AnimationMode.OpenDownwards)]
        public AnimationMode AnimationType { get; set; } = AnimationMode.OpenDownwards;
    
        [Flags]
        public enum AnimationMode : uint
        {
            CloseCentered = AW_CENTER | AW_HIDE
            CloseDownwards = AW_VER_POSITIVE | AW_HOR_POSITIVE | AW_HIDE
            CloseUpwards = AW_VER_NEGATIVE | AW_HOR_NEGATIVE | AW_HIDE
            OpenCentered = AW_CENTER
            OpenDownwards = AW_HOR_POSITIVE | AW_VER_POSITIVE
            OpenUpwards = AW_HOR_NEGATIVE | AW_VER_NEGATIVE
            SlideToRight = AW_SLIDE | AW_HOR_POSITIVE
            SlideToLeft = AW_SLIDE | AW_HOR_NEGATIVE
            SlideDownwards = AW_SLIDE | AW_VER_POSITIVE
            SlideUpwards = AW_SLIDE | AW_VER_NEGATIVE
            Hide = AW_HIDE
        }
    
        private const uint AW_HOR_POSITIVE = 0x1;
        private const uint AW_HOR_NEGATIVE = 0x2;
        private const uint AW_VER_POSITIVE = 0x4;
        private const uint AW_VER_NEGATIVE = 0x8;
        private const uint AW_CENTER = 0x10;
        private const uint AW_HIDE = 0x10000;
        private const uint AW_ACTIVATE = 0x20000;
        private const uint AW_SLIDE = 0x40000;
        private const uint AW_BLEND = 0x80000;
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool AnimateWindow(IntPtr hwnd, uint time, AnimationMode flags);
    }