문제

I have some menus that contain many menuitems. Mouse wheel doesn't scroll them. I have to use the keyboard arrows or click the arrows at top and bottom. Is it possible to use the mouse wheel to scroll toolstrip menu items? Thanks

도움이 되었습니까?

해결책

A working solution:

  1. Register for MouseWheel event of your form and DropDownClosed event of your root MenuStripItem (here, rootItem) in the Load event of the form

        this.MouseWheel += Form3_MouseWheel;
        rootItem.DropDownOpened += rootItem_DropDownOpened;
        rootItem.DropDownClosed += rootItem_DropDownClosed;
    
  2. Add the code for Keyboard class which simulate key presses

    public static class Keyboard
    {
        [DllImport("user32.dll")]
        static extern uint keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
    
    
        const byte VK_UP = 0x26; // Arrow Up key
        const byte VK_DOWN = 0x28; // Arrow Down key
    
        const int KEYEVENTF_EXTENDEDKEY = 0x0001; //Key down flag, the key is going to be pressed
        const int KEYEVENTF_KEYUP = 0x0002; //Key up flag, the key is going to be released
    
        public static void KeyDown()
        {
            keybd_event(VK_DOWN, 0, KEYEVENTF_EXTENDEDKEY, 0);
            keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0);
        }
    
        public static void KeyUp()
        {
            keybd_event(VK_UP, 0, KEYEVENTF_EXTENDEDKEY, 0);
            keybd_event(VK_UP, 0, KEYEVENTF_KEYUP, 0);
        }
    }
    
  3. Add the code for DropDownOpened, DropDownClosed, MouseWheel events:

    bool IsMenuStripOpen  = false;
    
    void rootItem_DropDownOpened(object sender, EventArgs e)
    {
        IsMenuStripOpen = true;
    }
    
    
    void rootItem_DropDownClosed(object sender, EventArgs e)
    {
        IsMenuStripOpen = false;
    }
    
    void Form3_MouseWheel(object sender, MouseEventArgs e)
    {
        if (IsMenuStripOpen)
        {
            if (e.Delta > 0)
            {
                Keyboard.KeyUp();
            }
            else
            {
                Keyboard.KeyDown();
            }
        }
    }
    

다른 팁

You can enable it application wide with this class:

public class DropDownMenuScrollWheelHandler : System.Windows.Forms.IMessageFilter
{
    private static DropDownMenuScrollWheelHandler Instance;
    public static void Enable(bool enabled)
    {
        if (enabled)
        {
            if (Instance == null)
            {
                Instance = new DropDownMenuScrollWheelHandler();
                Application.AddMessageFilter(Instance);
            }
        }
        else
        {
            if (Instance != null)
            {
                Application.RemoveMessageFilter(Instance);
                Instance = null;
            }
        }
    }
    private IntPtr activeHwnd;
    private ToolStripDropDown activeMenu;

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == 0x200 && activeHwnd != m.HWnd) // WM_MOUSEMOVE
        {
            activeHwnd = m.HWnd;
            this.activeMenu = Control.FromHandle(m.HWnd) as ToolStripDropDown;
        }
        else if (m.Msg == 0x20A && this.activeMenu != null) // WM_MOUSEWHEEL
        {
            int delta = (short)(ushort)(((uint)(ulong)m.WParam) >> 16);
            handleDelta(this.activeMenu, delta);
            return true;
        }
        return false;
    }

    private static readonly Action<ToolStrip, int> ScrollInternal
        = (Action<ToolStrip, int>)Delegate.CreateDelegate(typeof(Action<ToolStrip, int>),
            typeof(ToolStrip).GetMethod("ScrollInternal",
                System.Reflection.BindingFlags.NonPublic
                | System.Reflection.BindingFlags.Instance));

    private void handleDelta(ToolStripDropDown ts, int delta)
    {
        if (ts.Items.Count == 0)
            return;
        var firstItem = ts.Items[0];
        var lastItem = ts.Items[ts.Items.Count - 1];
        if (lastItem.Bounds.Bottom < ts.Height && firstItem.Bounds.Top > 0)
            return;
        delta = delta / -4;
        if (delta < 0 && firstItem.Bounds.Top - delta > 9)
        {
            delta = firstItem.Bounds.Top - 9;
        }
        else if (delta > 0 && delta > lastItem.Bounds.Bottom - ts.Height + 9)
        {
            delta = lastItem.Bounds.Bottom - owner.Height + 9;
        }
        if (delta != 0)
            ScrollInternal(ts, delta);
    }
}

This is very simply using a submenu (ToolStripMenuItem) of the context menu :

Assuming using a form1 (or UserControl) and a contextMenuStrip1 :

private void form1_Load( object sender , EventArgs e )
{
    //this.MouseWheel -= When_MouseWheel;
    this.MouseWheel += When_MouseWheel;
}
void When_MouseWheel( object sender , MouseEventArgs e )
{
    if ( this.contextMenuStrip1.IsDropDown ) {
         //this.Focus();
         if ( e.Delta > 0 ) SendKeys.SendWait( "{UP}" );
         else SendKeys.SendWait( "{DOWN}" );         
    }
}

I modified Mohsen Afshin's answer to click the up/down arrows instead of sending up/down key presses. My application had a ContextMenuStrip called menu. Here's the code.

In the initialization:

    menu.VisibleChanged += (s, e) =>
    {
        if (menu.Visible)
        {
            MouseWheel += ScrollMenu;
            menu.MouseWheel += ScrollMenu;
        }
        else
        {
            MouseWheel -= ScrollMenu;
            menu.MouseWheel -= ScrollMenu;
        }
    };

The ScrollMenu function:

[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);

private void ScrollMenu(object sender, MouseEventArgs e)
{
    Point origin = Cursor.Position;
    int clicks;

    if (e.Delta < 0)
    {
        Cursor.Position = menu.PointToScreen(new Point(menu.DisplayRectangle.Left + 5, menu.DisplayRectangle.Bottom + 5));
        clicks = e.Delta / -40;
    }
    else
    {
        Cursor.Position = menu.PointToScreen(new Point(menu.DisplayRectangle.Left + 5, menu.DisplayRectangle.Top - 5));
        clicks = e.Delta / 40;
    }

    for (int i = 0; i < clicks; i++)
        mouse_event(0x0006, 0, 0, 0, 0);//Left mouse button up and down on cursor position
    Cursor.Position = origin;
}

I was having trouble getting the mouse_event function to click a specific location, so I moved the cursor, clicked, and then moved the cursor back. It doesn't seem the cleanest, but it works.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top