Search code examples
.netvb.nettooltipballoon-tip

Display tooltip when a item of ComboBox exceeds the visible limit?


In my app, which is a WF project, I have a combobox with directories:

enter image description here

If a directory is too large the user can't see the entire directory name.

Now, in most applications I see that if a item of a combobox or textbox exceeds the visible limit then a kind of a little tooltip/ballontip is shown exactly at the item/mouse position displaying/expanding the full string (the full directory name I mean)

My question is how I can do the same, I don't know how to do this using the default tooltips.

UPDATE:

I've got this usercontrol, but it turns really slow when opening the dropdownlist and when while overhoving the combobox items, the "navigation" between the items are really slow and like I've said also is slow to open the dropdown list after do a click to expand the list.

I want to improve the speed like the default combobox:

PS: I don't know anything about C#

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

public class ComboBoxTooltip : ComboBox
{
    private DropdownWindow mDropdown;
    public delegate void DropdownItemSelectedEventHandler(object sender, DropdownItemSelectedEventArgs e);
    public event DropdownItemSelectedEventHandler DropdownItemSelected;

    protected override void OnDropDown(EventArgs e)
    {
        // Install wrapper
        base.OnDropDown(e);
        // Retrieve handle to dropdown list
        COMBOBOXINFO info = new COMBOBOXINFO();
        info.cbSize = Marshal.SizeOf(info);
        SendMessageCb(this.Handle, 0x164, IntPtr.Zero, out info);
        mDropdown = new DropdownWindow(this);
        mDropdown.AssignHandle(info.hwndList);
    }
    protected override void OnDropDownClosed(EventArgs e)
    {
        // Remove wrapper
        mDropdown.ReleaseHandle();
        mDropdown = null;
        base.OnDropDownClosed(e);
        OnSelect(-1, Rectangle.Empty, true);
    }
    internal void OnSelect(int item, Rectangle pos, bool scroll)
    {
        if (this.DropdownItemSelected != null)
        {
            pos = this.RectangleToClient(pos);
            DropdownItemSelected(this, new DropdownItemSelectedEventArgs(item, pos, scroll));
        }
    }
    // Event handler arguments
    public class DropdownItemSelectedEventArgs : EventArgs
    {
        private int mItem;
        private Rectangle mPos;
        private bool mScroll;
        public DropdownItemSelectedEventArgs(int item, Rectangle pos, bool scroll) { mItem = item; mPos = pos; mScroll = scroll; }
        public int SelectedItem { get { return mItem; } }
        public Rectangle Bounds { get { return mPos; } }
        public bool Scrolled { get { return mScroll; } }
    }

    // Wrapper for combobox dropdown list
    private class DropdownWindow : NativeWindow
    {
        private ComboBoxTooltip mParent;
        private int mItem;
        public DropdownWindow(ComboBoxTooltip parent)
        {
            mParent = parent;
            mItem = -1;
        }
        protected override void WndProc(ref Message m)
        {
            // All we're getting here is WM_MOUSEMOVE, ask list for current selection for LB_GETCURSEL
            Console.WriteLine(m.ToString());
            base.WndProc(ref m);
            if (m.Msg == 0x200)
            {
                int item = (int)SendMessage(this.Handle, 0x188, IntPtr.Zero, IntPtr.Zero);
                if (item != mItem)
                {
                    mItem = item;
                    OnSelect(false);
                }
            }
            if (m.Msg == 0x115)
            {
                // List scrolled, item position would change
                OnSelect(true);
            }
        }
        private void OnSelect(bool scroll)
        {
            RECT rc = new RECT();
            SendMessageRc(this.Handle, 0x198, (IntPtr)mItem, out rc);
            MapWindowPoints(this.Handle, IntPtr.Zero, ref rc, 2);
            mParent.OnSelect(mItem, Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom), scroll);
        }
    }
    // P/Invoke declarations
    private struct COMBOBOXINFO
    {
        public Int32 cbSize;
        public RECT rcItem;
        public RECT rcButton;
        public int buttonState;
        public IntPtr hwndCombo;
        public IntPtr hwndEdit;
        public IntPtr hwndList;
    }
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
    [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
    [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessageRc(IntPtr hWnd, int msg, IntPtr wp, out RECT lp);
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    [DllImport("user32.dll")]
    private static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] ref RECT rc, int points);
}

Solution

  • Well yo can try this:

        Dim LastSelectedItem As Int32 = -1
    
    Private Sub ComboBoxTooltip_DropdownItemSelected(sender As Object, e As ComboBoxTooltip.DropdownItemSelectedEventArgs) _
    Handles ComboBoxTooltip1.DropdownItemSelected
    
        Dim SelectedItem As Int32 = e.SelectedItem
    
        If SelectedItem <> LastSelectedItem Then
            ToolTip1.Hide(sender)
            LastSelectedItem = -1
        End If
    
        If SelectedItem < 0 OrElse e.Scrolled Then
            ToolTip1.Hide(sender)
            LastSelectedItem = -1
        Else
            If sender.Items(e.SelectedItem).Length > CInt(sender.CreateGraphics.MeasureString(0, sender.Font).Width) + 8 Then
                LastSelectedItem = SelectedItem
                ToolTip1.Show(sender.Items(SelectedItem).ToString(), sender, e.Bounds.Location)
            End If
        End If
    
    End Sub
    

    And the user control codes:

        using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    
    public class ComboBoxTooltip : ComboBox
    {
        private DropdownWindow mDropdown;
        public delegate void DropdownItemSelectedEventHandler(object sender, DropdownItemSelectedEventArgs e);
        public event DropdownItemSelectedEventHandler DropdownItemSelected;
    
        protected override void OnDropDown(EventArgs e)
        {
            // Install wrapper
            base.OnDropDown(e);
            // Retrieve handle to dropdown list
            COMBOBOXINFO info = new COMBOBOXINFO();
            info.cbSize = Marshal.SizeOf(info);
            SendMessageCb(this.Handle, 0x164, IntPtr.Zero, out info);
            mDropdown = new DropdownWindow(this);
            mDropdown.AssignHandle(info.hwndList);
        }
        protected override void OnDropDownClosed(EventArgs e)
        {
            // Remove wrapper
            mDropdown.ReleaseHandle();
            mDropdown = null;
            base.OnDropDownClosed(e);
            OnSelect(-1, Rectangle.Empty, true);
        }
        internal void OnSelect(int item, Rectangle pos, bool scroll)
        {
            if (this.DropdownItemSelected != null)
            {
                pos = this.RectangleToClient(pos);
                DropdownItemSelected(this, new DropdownItemSelectedEventArgs(item, pos, scroll));
            }
        }
        // Event handler arguments
        public class DropdownItemSelectedEventArgs : EventArgs
        {
            private int mItem;
            private Rectangle mPos;
            private bool mScroll;
            public DropdownItemSelectedEventArgs(int item, Rectangle pos, bool scroll) { mItem = item; mPos = pos; mScroll = scroll; }
            public int SelectedItem { get { return mItem; } }
            public Rectangle Bounds { get { return mPos; } }
            public bool Scrolled { get { return mScroll; } }
        }
    
        // Wrapper for combobox dropdown list
        private class DropdownWindow : NativeWindow
        {
            private ComboBoxTooltip mParent;
            private int mItem;
            public DropdownWindow(ComboBoxTooltip parent)
            {
                mParent = parent;
                mItem = -1;
            }
            protected override void WndProc(ref Message m)
            {
                // All we're getting here is WM_MOUSEMOVE, ask list for current selection for LB_GETCURSEL
                Console.WriteLine(m.ToString());
                base.WndProc(ref m);
                if (m.Msg == 0x200)
                {
                    int item = (int)SendMessage(this.Handle, 0x188, IntPtr.Zero, IntPtr.Zero);
                    if (item != mItem)
                    {
                        mItem = item;
                        OnSelect(false);
                    }
                }
                if (m.Msg == 0x115)
                {
                    // List scrolled, item position would change
                    OnSelect(true);
                }
            }
            private void OnSelect(bool scroll)
            {
                RECT rc = new RECT();
                SendMessageRc(this.Handle, 0x198, (IntPtr)mItem, out rc);
                MapWindowPoints(this.Handle, IntPtr.Zero, ref rc, 2);
                mParent.OnSelect(mItem, Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom), scroll);
            }
        }
        // P/Invoke declarations
        private struct COMBOBOXINFO
        {
            public Int32 cbSize;
            public RECT rcItem;
            public RECT rcButton;
            public int buttonState;
            public IntPtr hwndCombo;
            public IntPtr hwndEdit;
            public IntPtr hwndList;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
        private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
        [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
        private static extern IntPtr SendMessageRc(IntPtr hWnd, int msg, IntPtr wp, out RECT lp);
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
        [DllImport("user32.dll")]
        private static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] ref RECT rc, int points);
    }