Raise an event when I hover the mouse over a ComboBox item

You can create a Custom Control, derived from ComboBox, override its WndProc method to intercept the CB_GETCURSEL message.

Call base.WndProc(ref m) first. When the message is processed, the Message object’s m.Result property is set to a value (as IntPtr) that represents the Item currently tracked in the ListBox (the Item highlighted when the Mouse Pointer hovers it).

Note: prior to .Net Framework 4.8, the CB_GETCURSEL message result is not bubbled up automatically: we must send LB_GETCURSEL to the child ListBox to get the index of the Item currently highlighted.
The ListBox handle is retrieved using GetComboBoxInfo: it could be also accessed using reflection (the private ChildListAutomationObject property returns the ListBox AutomationElement, which provides the handle), or sending a CB_GETCOMBOBOXINFO message (but it’s the same as calling GetComboBoxInfo()).


This custom ComboBox raises an Event, ListItemSelectionChanged, with a custom EventArgs object, ListItemSelectionChangedEventArgs, which exposes two public properties: ItemIndex and ItemText, set to the Index and Text of the hovered item.


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

[DesignerCategory("Code")]
public class ComboBoxListEx : ComboBox
{
    private const int CB_GETCURSEL = 0x0147;
    private int listItem = -1;
    IntPtr listBoxHandle = IntPtr.Zero;

    public event EventHandler<ListItemSelectionChangedEventArgs> ListItemSelectionChanged;

    protected virtual void OnListItemSelectionChanged(ListItemSelectionChangedEventArgs e)
        => this.ListItemSelectionChanged?.Invoke(this, e);

    public ComboBoxListEx() { }

    // .Net Framework prior to 4.8 - get the handle of the ListBox
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        listBoxHandle = GetComboBoxListInternal(this.Handle);
    }

    protected override void WndProc(ref Message m)
    {
        int selItem = -1;
        base.WndProc(ref m);

        switch (m.Msg) {
            case CB_GETCURSEL:
                selItem = m.Result.ToInt32();
                break;
            // .Net Framework prior to 4.8
            // case CB_GETCURSEL can be left there or removed: it's always -1
            case 0x0134: 
                selItem = SendMessage(listBoxHandle, LB_GETCURSEL, 0, 0);
                break;
            default:
                // Add Case switches to handle other events
                break;
        }
        if (listItem != selItem) {
            listItem = selItem;
            OnListItemSelectionChanged(new ListItemSelectionChangedEventArgs(
                listItem, listItem < 0 ? string.Empty : GetItemText(Items[listItem]))
            );
        }
    }

    public class ListItemSelectionChangedEventArgs : EventArgs
    {
        public ListItemSelectionChangedEventArgs(int idx, string text) {
            ItemIndex = idx;
            ItemText = text;
        }
        public int ItemIndex { get; private set; }
        public string ItemText { get; private set; }
    }

    // -------------------------------------------------------------
    // .Net Framework prior to 4.8
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, int lParam);
    
    private const int LB_GETCURSEL = 0x0188;

    [StructLayout(LayoutKind.Sequential)]
    internal struct COMBOBOXINFO
    {
        public int cbSize;
        public Rectangle rcItem;
        public Rectangle rcButton;
        public int buttonState;
        public IntPtr hwndCombo;
        public IntPtr hwndEdit;
        public IntPtr hwndList;
        public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
    }

    internal static IntPtr GetComboBoxListInternal(IntPtr cboHandle)
    {
        var cbInfo = new COMBOBOXINFO();
        cbInfo.Init();
        GetComboBoxInfo(cboHandle, ref cbInfo);
        return cbInfo.hwndList;
    }
}

Works like this:

ComboBox Custom List Hover Selection Events

Leave a Comment