自定义.NET ComboBox控件

时间:2009-12-08 18:56:24

标签: listview combobox

嘿伙计们。我正在WinForms中工作并尝试创建一个ComboBox控件,当它下拉时会托管ListView。我正在使用ToolStripDropDown和ToolStripControlHost来完成此任务。

我的问题是,当我将DropDownStyle设置为 DropDownList 时,我无法在ComboBox中显示所选的ListView项。显然,只有当实际的ComboBox在其集合中包含项时,此行为才有效。我最终手动将项目添加到我的OnDoubleClick事件中的ComboBox,并将其选定的索引设置为0以显示该项目。但这仍然行不通。我没有想法。

这是我的ListViewComboBox类

using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    [ToolboxItem(true)]
    [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public class ListViewComboBox : ComboBox
{
    private ToolStripControlHost _controlHost;
    private ToolStripDropDown _dropDown;

    public ListViewComboBox()
    {
        ListView innerListView = new ListView();
        innerListView.BorderStyle = BorderStyle.None;
        innerListView.View = View.SmallIcon;
        innerListView.MultiSelect = false;
        innerListView.HoverSelection = true;
        innerListView.DoubleClick += new System.EventHandler(listView_DoubleClick);

        _controlHost = new ToolStripControlHost(innerListView);
        _controlHost.AutoSize = false;

        _dropDown = new ToolStripDropDown();
        _dropDown.Items.Add(_controlHost);
    }

    public void AutoSizeItems()
    {
        foreach (ListViewItem item in Items)
        {
            Size size = TextRenderer.MeasureText(item.Text, item.Font);
            ListView lstView = (ListView)_controlHost.Control;

            if (size.Width >= this.DropDownWidth)
            {
                this.DropDownWidth = size.Width + 40;
            }
        }
    }

    void listView_DoubleClick(object sender, System.EventArgs e)
    {
        if (sender is ListView)
        {
            ListView control = (ListView)sender;

            base.Items.Clear();

            if (control.SelectedItems.Count > 0)
            {
                base.Items.Add(control.SelectedItems[0].Text);
                base.Text = control.SelectedItems[0].Text;
                this.SelectedIndex = 0;
                this.Focus();
            }

            _controlHost.PerformClick();
        }
    }

    public new ListView.ListViewItemCollection Items
    {
        get
        {
            return ((ListView)_controlHost.Control).Items;
        }
    }

    public ListViewGroupCollection Groups
    {
        get
        {
            return ((ListView)_controlHost.Control).Groups;
        }
    }

    public new ListViewItem SelectedItem
    {
        get
        {
            return ((ListView)_controlHost.Control).SelectedItems[0];
        }
    }

    private void ShowDropDown()
    {
        if (_dropDown != null)
        {
            _controlHost.Width = this.DropDownWidth;
            _controlHost.Height = this.DropDownHeight;

            _dropDown.Show(this, 0, this.Height);
            _controlHost.Focus();
        }
    }

    private const int WM_USER = 0x0400,
                      WM_REFLECT = WM_USER + 0x1C00,
                      WM_COMMAND = 0x0111,
                      CBN_DROPDOWN = 7,
                      LVM_FIRST = 0x1000,
                      LVM_SETCOLUMNWIDTH = (LVM_FIRST + 30);

    public static int HIWORD(int n)
    {
        return (n >> 16) & 0xffff;
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == (WM_REFLECT + WM_COMMAND))
        {
            if (HIWORD((int)m.WParam) == CBN_DROPDOWN)
            {
                ShowDropDown();
                return;
            }
        }
        base.WndProc(ref m);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_dropDown != null)
            {
                _dropDown.Dispose();
                _dropDown = null;
            }
        }
        base.Dispose(disposing);
    }
}
}

这就是我使用上述控件的方式:

    public Form1()
    {
        InitializeComponent();

        ListViewItem item = new ListViewItem();
        item.Font = new Font(item.Font.FontFamily.Name, 9, FontStyle.Bold);
        item.ForeColor = Color.MediumBlue;
        item.Text = "Test Entry";
        listViewComboBox1.Items.Add(item);
        listViewComboBox1.AutoSizeItems();
    }

1 个答案:

答案 0 :(得分:0)

问题与将控件上的绘制模式设置为所有者绘制有关。如果有人感兴趣,这里是最终控件的样子。解决方案并非真正的最终实现,更多的是概念验证。它需要进行重大重构才能更好地模拟下拉控件的功能,并且通用性足以托管多个控件。但是我会把它留给以后。

[ToolboxItem(true)]
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public class ListViewComboBox : ComboBox
{
    #region Fields

    /// <summary>
    /// Used as the list view host.
    /// </summary>
    private readonly ToolStripControlHost _controlHost;

    /// <summary>
    /// Used as the pop-up window.
    /// </summary>
    private ToolStripDropDown _dropDown;

    /// <summary>
    /// Search input area.
    /// </summary>
    private ToolStripTextBox _searchBox;

    /// <summary>
    /// Contains tooltip instance used to show tooltip over list view item.
    /// </summary>
    private ToolTip _toolTip;

    /// <summary>
    /// Indicates whether the list view has been dropped down.
    /// </summary>
    private bool _dropped;

    /// <summary>
    /// Indicates whether the list view has been auto-sized.
    /// </summary>
    private bool _autoSized;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a new instance of <see cref="ListViewComboBox"/>.
    /// </summary>
    public ListViewComboBox()
    {
        ListView innerListView = new ListView();
        innerListView.Columns.Add("Vendors");
        innerListView.HeaderStyle = ColumnHeaderStyle.None; // Hide column headers
        innerListView.BorderStyle = BorderStyle.None;
        innerListView.View = View.Details; // 1 item per line
        innerListView.MultiSelect = false;
        innerListView.FullRowSelect = true;
        innerListView.HideSelection = false;
        innerListView.ShowGroups = true;
        innerListView.ShowItemToolTips = false;
        innerListView.DoubleClick += new System.EventHandler(ListView_DoubleClick);
        innerListView.ItemMouseHover += new ListViewItemMouseHoverEventHandler(ListView_ItemMouseHover);

        // Default height for now
        DropDownHeight = 300;

        _controlHost = new ToolStripControlHost(innerListView);
        _controlHost.AutoSize = false;

        _toolTip = new ToolTip();

        _searchBox = new ToolStripTextBox();
        _searchBox.BorderStyle = BorderStyle.FixedSingle;
        _searchBox.Dock = DockStyle.Fill;
        _searchBox.Text = "Search";
        _searchBox.Width = Width;
        _searchBox.TextChanged += new EventHandler(SearchBox_TextChanged);
        _searchBox.LostFocus += new EventHandler(SearchBox_LostFocus);
        _searchBox.GotFocus += new EventHandler(SearchBox_GotFocus);

        _dropDown = new ToolStripDropDown();
        _dropDown.DropShadowEnabled = false;
        _dropDown.Items.Add(_searchBox);
        _dropDown.Items.Add(_controlHost);
    }

    #endregion

    #region Event Handlers

    /// <summary>
    /// Selects the item clicked in the list view and updates
    /// combo box to display the selected item's text value.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ListView_DoubleClick(object sender, EventArgs e)
    {
        if (sender is ListView)
        {
            ListView control = (ListView)sender;

            DataSource = null;

            if (control.SelectedItems.Count > 0)
            {
                // Bind the first selected item to the combo box
                // to display the item's textual value
                IList<ListViewItem> source = new List<ListViewItem>(1);
                source.Add(control.SelectedItems[0]);

                DataSource = source;
                DisplayMember = "Text";
                Tag = control.SelectedItems[0].Tag;
                SelectedIndex = 0;
            }

            HideDropDown();
        }
    }

    /// <summary>
    /// Applies tooltip on item hover.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ListView_ItemMouseHover(object sender, ListViewItemMouseHoverEventArgs e)
    {
        if (!string.IsNullOrEmpty(e.Item.ToolTipText))
        {
            _toolTip.SetToolTip(InnerControl, e.Item.ToolTipText);
        }
        else
        {
            _toolTip.SetToolTip(InnerControl, e.Item.Text);
        }
    }

    /// <summary>
    /// Clears search box when focus is set.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void SearchBox_GotFocus(object sender, EventArgs e)
    {
        _searchBox.Clear();
    }

    /// <summary>
    /// Titles the search box when focus is lost.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void SearchBox_LostFocus(object sender, EventArgs e)
    {
        _searchBox.Text = "Search";
    }

    /// <summary>
    /// Performs search on search box text change.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void SearchBox_TextChanged(object sender, EventArgs e)
    {
        ListViewItem item = InnerControl.FindItemWithText(_searchBox.Text);

        if (item != null)
        {
            InnerControl.SelectedItems.Clear();
            InnerControl.EnsureVisible(item.Index);
            item.Selected = true;
            item.Focused = true;
            InnerControl.Select();
        }
    }

    #endregion

    #region Properties

    /// <summary>
    /// Provides access to the item collection.
    /// </summary>
    public new ListView.ListViewItemCollection Items
    {
        get
        {
            return InnerControl.Items;
        }
    }

    /// <summary>
    /// Provides access to the group collection.
    /// </summary>
    public ListViewGroupCollection Groups
    {
        get
        {
            return InnerControl.Groups;
        }
    }

    /// <summary>
    /// Gets selected item.
    /// </summary>
    public new ListViewItem SelectedItem
    {
        get
        {
            if (InnerControl.SelectedItems.Count > 0)
            {
                return InnerControl.SelectedItems[0];
            }

            return null;
        }
    }

    /// <summary>
    /// Gets selected item.
    /// </summary>
    public new object SelectedValue
    {
        get
        {
            return SelectedItem;
        }
    }

    /// <summary>
    /// Gets selected text.
    /// </summary>
    public new string SelectedText
    {
        get
        {
            if (SelectedItem != null)
            {
                return SelectedItem.Text;
            }

            return null;
        }
    }

    protected ListView InnerControl
    {
        get
        {
            return _controlHost.Control as ListView;
        }
    }

    #endregion

    #region Public Members

    /// <summary>
    /// Auto-sizes the drop-down based on the content lengths.
    /// </summary>
    public void AutoSizeItems()
    {
        AutoSizeInternal(InnerControl, false);
    }

    /// <summary>
    /// Sets an item in the list as selected.
    /// </summary>
    /// <param name="value">The item value to search for.</param>
    public void SetValue(string value)
    {
        ListViewItem item = InnerControl.FindItemWithText(value);

        if (item != null)
        {
            item.Selected = true;

            IList<ListViewItem> source = new List<ListViewItem>(1);
            source.Add(item);

            DataSource = source;
            DisplayMember = "Text";
            Tag = item.Tag;
            SelectedIndex = 0;
        }
    }

    /// <summary>
    /// Opens the drop-down.
    /// </summary>
    public void ShowDropDown()
    {
        if (_dropDown != null)
        {
            _controlHost.Width = DropDownWidth;
            _controlHost.Height = DropDownHeight;

            // Highlight selected item
            if (!string.IsNullOrEmpty(Text))
            {
                ListViewItem foundItem = InnerControl.FindItemWithText(Text);

                if (foundItem != null)
                {
                    foundItem.Selected = true;
                    foundItem.EnsureVisible();
                }
                else
                {
                    // Clear items which were clicked, but not selected
                    if (InnerControl.SelectedItems.Count > 0)
                    {
                        InnerControl.SelectedItems.Clear();
                    }
                }
            }

            _dropDown.Show(this, 0, Height);
            _controlHost.Focus();
            _dropped = true;
        }
    }

    /// <summary>
    /// Collapses the drop-down.
    /// </summary>
    public void HideDropDown()
    {
        if (_dropDown != null)
        {
            _dropDown.Hide();
            _dropped = false;
        }
    }

    #endregion

    #region Protected Members

    /// <summary>
    /// Process Win32 loop messages.
    /// </summary>
    /// <param name="m">The message to process.</param>
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == (NativeMethods.WM_REFLECT + NativeMethods.WM_COMMAND))
        {
            if (NativeMethods.HIWORD((int)m.WParam) == NativeMethods.CBN_DROPDOWN)
            {
                // Prevent ComboBox from dropping down its list
                RecreateHandle();
                NativeMethods.SendMessage(Handle, NativeMethods.CB_SHOWDROPDOWN, 0, 0);

                // Autosize the list view
                if (!_autoSized)
                {
                    AutoSizeItems();
                    _autoSized = true;
                }

                // Show/Hide dropped down based on current state
                if (!_dropped)
                {
                    ShowDropDown();
                }
                else
                {
                    HideDropDown();
                }

                return;
            }
        }

        base.WndProc(ref m);
    }

    /// <summary>
    /// Disposes the control and releases memory.
    /// </summary>
    /// <param name="disposing"></param>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_dropDown != null)
            {
                _dropDown.Dispose();
                _dropDown = null;
            }
        }

        if (_toolTip != null)
        {
            _toolTip.Dispose();
            _toolTip = null;
        }

        base.Dispose(disposing);
    }

    #endregion

    #region Helper Members

    private void AutoSizeInternal(ListView lv, bool adjustForVerticalScrollBar)
    {
        int resizeByContent = 0;
        int resizeByHeader = 0;

        for (int i = 0; i < lv.Columns.Count; i++)
        {
            lv.Columns[i].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
            resizeByContent = lv.Columns[i].Width;

            lv.Columns[i].AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
            resizeByHeader = lv.Columns[i].Width;

            lv.Columns[i].Width = (resizeByHeader > resizeByContent) ? resizeByHeader : resizeByContent;

            DropDownWidth = lv.Columns[i].Width + 20;

            if (DropDownWidth < Width)
            {
                DropDownWidth = Width;
            }
        }
    }

    #endregion
}

#region Native Methods Helper

internal static class NativeMethods
{
    #region Win32 API Stubs

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    public static extern int SendMessage(IntPtr handle, int message, int wparam, int lparam);

    #endregion

    #region Constants

    internal const int WM_USER = 0x0400,
                       WM_REFLECT = WM_USER + 0x1C00,
                       WM_COMMAND = 0x0111,
                       WM_PAINT = 0xf,
                       CBN_DROPDOWN = 7,
                       CB_SHOWDROPDOWN = 0x14F;

    #endregion

    internal static int HIWORD(int n)
    {
        return (n >> 16) & 0xffff;
    }
}

#endregion