How to get the index of a ComboBox Item under the cursor?

我与影子孤独终老i 提交于 2020-05-17 03:46:49

问题


In a recent project of mine I had a ComboBox with items that exceeded the ComboBox drop-down list length.
I wanted to show the items full text in a tool tip.
Unfortunately, the .NET framework doesn't provide a built-in solution for this.


回答1:


Here's my own solution

You can find the full code, a more detailed explanation and an example project on my github. https://github.com/MaxBGitHub/MouseOverItemComboBox

Prerequisites

The provided code is for the Windows Forms library of the C# .NET 4.5.2 framework and was written with Visual Studio 2017.

In order to get this to work you need to utilize some native WinAPI functions and structs. The following native functions and native structs will be used:

  • RECT
  • POINT
  • COMBOBOXINFO
  • LBItemFromPt
  • GetComboBoxInfo

Just copy paste the structs from pinvoke. No need to wrap your head around these. The LBItemFromPt function and the GetComboBoxInfo function are pretty straight forward. If you do not know how to implement these, just go to the github page above or read the code below.

Custom ComboBox class implementation

using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System;


class MouseOverItemComboBox : ComboBox
{
    // Event delegate
    public delegate void OnMouseOverItem(object sender, MouseOverEventArgs e);
    // Event which the parent can subscribe to.
    public event OnMouseOverItem MouseOverItem;

    // The possible combo box button states.
    enum ButtonState {
        STATE_SYSTEM_NONE       = 0,            // Button exists and is not pressed.
        STATE_SYSTEM_INVISIBLE  = 0x00008000,   // There is no button.
        STATE_SYSTEM_PRESSED    = 0x00000008,   // Button is pressed.
    }

    /* Native COMBOBOXINFO struct implementation.
    ** Contains combo box status information. */
    [StructLayout(LayoutKind.Sequential)]
    struct COMBOBOXINFO
    {
        public int          cbSize;         // Size in bytes of struct.
        public RECT         rcItem;         // RECT that specifies the coordinates of the edit box.
        public RECT         rcButton;       // RECT that specifies the coordinates of the drop-down button.
        public ButtonState  stateButton;    // Drop-down button state.
        public IntPtr       hwndCombo;      // Handle to the combo box.
        public IntPtr       hwndEdit;       // Handle to the edit box.
        public IntPtr       hwndList;       // Handle to the drop-down list.
    }


    /* Sent to parent window of a list box before the system draws the list box.
    ** Can set text and background color of the list box by using the specified
    ** device context handle. */
    const int WM_CTLCOLORLISTBOX = 0x0134;


    // Native function that retreives information about the specified combo box.
    // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcomboboxinfo
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool GetComboBoxInfo(IntPtr hWnd, [In][Out] ref COMBOBOXINFO pcbi);

    // Native function that retreives the index of the item at the specified point in a list box.
    // https://docs.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-lbitemfrompt
    [DllImport("Comctl32.dll", SetLastError = true)]
    static extern int LBItemFromPt(IntPtr hLB, POINT pt, bool bAutoScroll);


    // Helper method which will invoke the MouseOverItem event.
    private void HandleMouseOverItem()
    {
        // cbSize must be set before calling GetComboBoxInfo.
        COMBOBOXINFO pcbi = new COMBOBOXINFO();         
        pcbi.cbSize = Marshal.SizeOf(pcbi);

        // Get combo box information.
        GetComboBoxInfo(Handle, ref pcbi);
        // Check for invalid pointer... just in case.
        if (pcbi.hwndList == IntPtr.Zero)
            return;

        // Current position of cursor.
        POINT pt = Cursor.Position;
        // LBItemFromPt will return the Index of the Item on success.
        int retVal = LBItemFromPt(pcbi.hwndList, pt, false);
        if (retVal == -1)
            return;

        // Invoke the event.
        MouseOverItem?.Invoke(this, new MouseOverEventArgs(retVal));
    }


    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            /* This message is sent by the list box of the combo box.
            ** It is sent before the system draws the list box.
            ** Whenever the cursor enters a list item this message will be
            ** sent to the parent i.e. the combox box.
            ** NOTE that this message is always sent twice.
            ** First time for drawing the default item background.
            ** Second time for drawing the highlighted item background. */
            case WM_CTLCOLORLISTBOX:
                {
                    // Let the helper method do the rest.
                    HandleMouseOverItem();
                    base.WndProc(ref m);
                    break;
                }
            default:
                {
                    base.WndProc(ref m);
                    break;
                }
        }
    }
}


来源:https://stackoverflow.com/questions/61748198/how-to-get-the-index-of-a-combobox-item-under-the-cursor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!