允许每个项目在Winforms组合框(或列表框)中使用多行

允许每个项目在Winforms组合框(或列表框)中使用多行,winforms,Winforms,这能相对容易地完成吗?我能在15分钟内完成以下工作,所以是的。其主要思想是处理DrawItem事件 下面是我对这个问题的看法(你可以看到另一个例子,在项目中绘制图标) 公共部分类表单1:表单 { 公共表格1() { 初始化组件(); this.comboBox1.DrawMode=DrawMode.OwnerDrawVariable; this.comboBox1.DrawItem+=新的DrawItemEventHandler(comboBox1\u DrawItem); this.combo

这能相对容易地完成吗?

我能在15分钟内完成以下工作,所以是的。其主要思想是处理DrawItem事件

下面是我对这个问题的看法(你可以看到另一个例子,在项目中绘制图标)

公共部分类表单1:表单
{
公共表格1()
{
初始化组件();
this.comboBox1.DrawMode=DrawMode.OwnerDrawVariable;
this.comboBox1.DrawItem+=新的DrawItemEventHandler(comboBox1\u DrawItem);
this.comboBox1.Items.Add(“需要占用两行的一些文本…”);
this.comboBox1.ItemHeight=30;
}
IEnumerable包装字符串(字符串str、图形g、字体、,
整数允许宽度)
{            
字符串[]arr=str.Split(“”);
StringBuilder当前=新建StringBuilder();
foreach(arr中的字符串标记)
{                
//TODO:你必须解决这个问题,可能会在边缘情况下出现断裂
整数宽度=
(int)g.MeasureString(当前.ToString()+“”+标记,字体).Width;
如果(宽度>允许宽度)
{
产生返回电流。ToString();
current.Clear();
}
当前。追加(标记+“”);
}
产生返回电流。ToString();
}
无效comboBox1\u DrawItem(对象发送方,DrawItemEventArgs e)
{
画笔背景画笔,背景画笔;
如果(e.State==(DrawItemState.Selected |
DrawItemState.NoAccelerator | DrawItemState.NoFocusRect)||
e、 状态==DrawItemState.Selected)
{
forgroundBrush=画笔。黑色;
背景画笔=画笔。白色;
}
其他的
{
forgroundBrush=画笔。白色;
背景画笔=画笔。黑色;
}
//包装字符串的某种方法(在空格上)
string str=(string)comboBox1.Items[e.Index];
矩形rc=
新矩形(e.Bounds.X,e.Bounds.Y,e.Bounds.Width,e.Bounds.Height);
e、 图形.圆角矩形(圆形笔刷,rc);
整数字符串高度=
(int)例如图形。测量(str,comboBox1.Font)。高度;
int lineNo=0;
foreach(字符串行输入)
包装字符串(str、e.图形、comboBox1.字体、e.边界、宽度))
{
e、 图形.抽绳(线条,组合框1.字体,背景刷,
新的点F(0,行号*stringHeight+5);
lineNo++;
}            
}             
}
用法:创建一个常规表单并在其上放置一个组合框


(请注意,这当然只是一个简单的概念证明——显然还有改进的余地。而且它只是假设只有两行而不是一行。但它表明这是可能的。)

我发现Tim Mackey制作的这个类在我的项目中效果非常好():

C版本:

使用系统;
使用系统图;
使用System.Linq;
使用System.Windows.Forms;
使用System.Runtime.InteropServices;
使用System.Collections.Generic;
名称空间hortlaptopap
{
类ComboBoxWrap:ComboBox
{            
[DllImport(“user32.dll”)]
[返回:Marshallas(UnmanagedType.Bool)]
公共静态外部bool GetWindowRect(IntPtr hwnd,out RECT lpRect);
[DllImport(“user32.dll”,SetLastError=true)]
[返回:Marshallas(UnmanagedType.Bool)]
私有静态外部bool SetWindowPos(IntPtr hWnd、IntPtr hwninsertafter、intx、inty、intcx、intcy、uint uFlags);
[StructLayout(LayoutKind.Sequential)]
公共结构矩形
{
public int Left;//左上角的x位置
public int Top;//左上角的y位置
public int Right;//右下角的x位置
public int Bottom;//右下角y位置
}
公共常数int SWP_NOZORDER=0x0004;
公共常数int SWP_NOACTIVE=0x0010;
公共常量int SWP_FRAMECHANGED=0x0020;
公共常数int SWP_NOOWNERZORDER=0x0200;
public const int WM_CTLCOLORLISTBOX=0x0134;
私有int hwndropdown=0;
受保护的覆盖无效WndProc(参考消息m)
{
if(m.Msg==WM_CTLCOLORLISTBOX)
{
如果(_hwndropdown==0)
{
_hwndropdown=m.LParam.ToInt32();
矩形r;
GetWindowRect((IntPtr)\u hwndropdown,out r);
//int newHeight=0;

//对于(int i=0;i这个问题有点老了,但是我发现带有
RectangleF
参数的
DrawString
不仅将文本剪辑到该矩形,而且还将其包装

示例代码:

StringFormat sf=StringFormat.genericyprographic;
sf.Trimming=StringTrimming.EllipsisCharacter;
g、 绘图字符串(文本、字体、前导笔、e.边界、sf);

另请参见

我想知道为什么此解决方案会将ComboBox子类化并处理DrawItem事件?为什么有必要?他似乎在根据额外高度调整窗口的位置,但我没有看到其他解决方案需要此选项供参考。设置下拉高度的行可能会导致ComboBox需要多次单击在它打开之前。只需将结果存储在一个局部变量中,并在调用SetWindowPos时使用它。它仍然可以工作,但不会破坏下拉框的打开。
public partial class Form1 : Form  
{
    public Form1()
    {
        InitializeComponent();

        this.comboBox1.DrawMode = DrawMode.OwnerDrawVariable;            
        this.comboBox1.DrawItem += new DrawItemEventHandler(comboBox1_DrawItem);
        this.comboBox1.Items.Add("Some text that needs to be take up two lines...");
        this.comboBox1.ItemHeight = 30;
    }

    IEnumerable<string> WrapString(string str, Graphics g, Font font, 
                                   int allowedWidth)
    {            
        string[] arr = str.Split(' ');            
        StringBuilder current = new StringBuilder();
        foreach (string token in arr)
        {                
            // TODO: You'll have to fix this, might break in marginal cases
            int width = 
              (int)g.MeasureString(current.ToString() + " " + token, font).Width;
            if (width > allowedWidth)
            {
                yield return current.ToString();
                current.Clear();
            }

            current.Append(token + " ");

        }
        yield return current.ToString();
    }

    void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
    {
        Brush backgroundBrush, forgroundBrush;

        if (e.State == (DrawItemState.Selected | 
                    DrawItemState.NoAccelerator | DrawItemState.NoFocusRect) ||
            e.State == DrawItemState.Selected) 
        {
            forgroundBrush = Brushes.Black;
            backgroundBrush = Brushes.White;
        }
        else
        {
            forgroundBrush = Brushes.White;
            backgroundBrush = Brushes.Black;
        }

        // some way to wrap the string (on a space)           
        string str = (string)comboBox1.Items[e.Index];


        Rectangle rc = 
           new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
        e.Graphics.FillRectangle(forgroundBrush, rc);

        int stringHeight = 
               (int)e.Graphics.MeasureString(str, comboBox1.Font).Height;
        int lineNo = 0;
        foreach (string line in 
                      WrapString(str, e.Graphics, comboBox1.Font, e.Bounds.Width))
        {
            e.Graphics.DrawString(line, comboBox1.Font, backgroundBrush, 
                                  new PointF(0, lineNo * stringHeight + 5));
            lineNo++;
        }            
    }             
}
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace HortLaptopApp
{
    class ComboBoxWrap : ComboBox
    {            
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;        // x position of upper-left corner
            public int Top;         // y position of upper-left corner
            public int Right;       // x position of lower-right corner
            public int Bottom;      // y position of lower-right corner
        }

        public const int SWP_NOZORDER = 0x0004;
        public const int SWP_NOACTIVATE = 0x0010;
        public const int SWP_FRAMECHANGED = 0x0020;
        public const int SWP_NOOWNERZORDER = 0x0200;

        public const int WM_CTLCOLORLISTBOX = 0x0134;

        private int _hwndDropDown = 0;

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_CTLCOLORLISTBOX)
            {
                if (_hwndDropDown == 0)
                {
                    _hwndDropDown = m.LParam.ToInt32();

                    RECT r;
                    GetWindowRect((IntPtr)_hwndDropDown, out r);

                    //int newHeight = 0;
                   // for(int i=0; i<Items.Count && i < MaxDropDownItems; i++)
                    //    newHeight += this.GetItemHeight(i);

                    int total = 0;
                    for (int i = 0; i < this.Items.Count; i++)
                        total += this.GetItemHeight(i);
                    this.DropDownHeight = total + SystemInformation.BorderSize.Height * (this.Items.Count + 2);


                    SetWindowPos((IntPtr)_hwndDropDown, IntPtr.Zero,
                        r.Left,
                                 r.Top,
                                 DropDownWidth,
                                 DropDownHeight,
                                 SWP_FRAMECHANGED |
                                     SWP_NOACTIVATE |
                                     SWP_NOZORDER |
                                     SWP_NOOWNERZORDER);
                }
            }

            base.WndProc(ref m);
        }

        protected override void OnDropDownClosed(EventArgs e)
        {
            _hwndDropDown = 0;
            base.OnDropDownClosed(e);
        }

        public ComboBoxWrap() : base()
        {
            // add event handlers
            this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
            this.DrawItem += new DrawItemEventHandler(ComboBoxWrap_DrawItem);
            this.MeasureItem += new MeasureItemEventHandler(ComboBoxWrap_MeasureItem);
        }

        void ComboBoxWrap_MeasureItem(object sender, MeasureItemEventArgs e)
        {
            // set the height of the item, using MeasureString with the font and control width
            ComboBoxWrap ddl = (ComboBoxWrap)sender;
            string text = ddl.Items[e.Index].ToString();
            SizeF size = e.Graphics.MeasureString(text, this.Font, ddl.DropDownWidth); 
            e.ItemHeight = (int)Math.Ceiling(size.Height) + 1;  // plus one for the border
            e.ItemWidth = ddl.DropDownWidth;
            System.Diagnostics.Trace.WriteLine(String.Format("Height {0}, Text {1}", e.ItemHeight, text));
        }

        void ComboBoxWrap_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index < 0)
                return;

            // draw a lighter blue selected BG colour, the dark blue default has poor contrast with black text on a dark blue background
            if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
                e.Graphics.FillRectangle(Brushes.PowderBlue, e.Bounds);
            else
                e.Graphics.FillRectangle(Brushes.White, e.Bounds);

            // get the text of the item
            ComboBoxWrap ddl = (ComboBoxWrap)sender;
            string text = ddl.Items[e.Index].ToString();

            // don't dispose the brush afterwards
            Brush b = Brushes.Black;
            e.Graphics.DrawString(text, this.Font, b, e.Bounds, StringFormat.GenericDefault);

            // draw a light grey border line to separate the items
            Pen p = new Pen(Brushes.Gainsboro, 1);
            e.Graphics.DrawLine(p, new Point(e.Bounds.Left, e.Bounds.Bottom-1), new Point(e.Bounds.Right, e.Bounds.Bottom-1));
            p.Dispose();

            e.DrawFocusRectangle();
        }
    }
}
Imports System.Drawing
Imports System.Linq
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.Collections.Generic

Namespace HortLaptopApp
    Class ComboBoxWrap
        Inherits ComboBox           
        <DllImport("user32.dll")> _
        Public Shared Function GetWindowRect(hwnd As IntPtr, ByRef lpRect As RECT) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <DllImport("user32.dll", SetLastError := True)> _
        Private Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, _
            uFlags As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <StructLayout(LayoutKind.Sequential)> _
        Public Structure RECT
            Public Left As Integer
            ' x position of upper-left corner
            Public Top As Integer
            ' y position of upper-left corner
            Public Right As Integer
            ' x position of lower-right corner
            Public Bottom As Integer
            ' y position of lower-right corner
        End Structure

        Public Const SWP_NOZORDER As Integer = &H4
        Public Const SWP_NOACTIVATE As Integer = &H10
        Public Const SWP_FRAMECHANGED As Integer = &H20
        Public Const SWP_NOOWNERZORDER As Integer = &H200

        Public Const WM_CTLCOLORLISTBOX As Integer = &H134

        Private _hwndDropDown As Integer = 0

        Protected Overrides Sub WndProc(ByRef m As Message)
            If m.Msg = WM_CTLCOLORLISTBOX Then
                If _hwndDropDown = 0 Then
                    _hwndDropDown = m.LParam.ToInt32()

                    Dim r As RECT
                    GetWindowRect(DirectCast(_hwndDropDown, IntPtr), r)

                    'int newHeight = 0;
                    ' for(int i=0; i<Items.Count && i < MaxDropDownItems; i++)
                    '    newHeight += this.GetItemHeight(i);

                    Dim total As Integer = 0
                    For i As Integer = 0 To Me.Items.Count - 1
                        total += Me.GetItemHeight(i)
                    Next
                    Me.DropDownHeight = total + SystemInformation.BorderSize.Height * (Me.Items.Count + 2)


                    SetWindowPos(DirectCast(_hwndDropDown, IntPtr), IntPtr.Zero, r.Left, r.Top, DropDownWidth, DropDownHeight, _
                        SWP_FRAMECHANGED Or SWP_NOACTIVATE Or SWP_NOZORDER Or SWP_NOOWNERZORDER)
                End If
            End If

            MyBase.WndProc(m)
        End Sub

        Protected Overrides Sub OnDropDownClosed(e As EventArgs)
            _hwndDropDown = 0
            MyBase.OnDropDownClosed(e)
        End Sub

        Public Sub New()
            MyBase.New()
            ' add event handlers
            Me.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable
            Me.DrawItem += New DrawItemEventHandler(AddressOf ComboBoxWrap_DrawItem)
            Me.MeasureItem += New MeasureItemEventHandler(AddressOf ComboBoxWrap_MeasureItem)
        End Sub

        Private Sub ComboBoxWrap_MeasureItem(sender As Object, e As MeasureItemEventArgs)
            ' set the height of the item, using MeasureString with the font and control width
            Dim ddl As ComboBoxWrap = DirectCast(sender, ComboBoxWrap)
            Dim text As String = ddl.Items(e.Index).ToString()
            Dim size As SizeF = e.Graphics.MeasureString(text, Me.Font, ddl.DropDownWidth)
            e.ItemHeight = CInt(Math.Ceiling(size.Height)) + 1
            ' plus one for the border
            e.ItemWidth = ddl.DropDownWidth
            System.Diagnostics.Trace.WriteLine([String].Format("Height {0}, Text {1}", e.ItemHeight, text))
        End Sub

        Private Sub ComboBoxWrap_DrawItem(sender As Object, e As DrawItemEventArgs)
            If e.Index < 0 Then
                Return
            End If

            ' draw a lighter blue selected BG colour, the dark blue default has poor contrast with black text on a dark blue background
            If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
                e.Graphics.FillRectangle(Brushes.PowderBlue, e.Bounds)
            Else
                e.Graphics.FillRectangle(Brushes.White, e.Bounds)
            End If

            ' get the text of the item
            Dim ddl As ComboBoxWrap = DirectCast(sender, ComboBoxWrap)
            Dim text As String = ddl.Items(e.Index).ToString()

            ' don't dispose the brush afterwards
            Dim b As Brush = Brushes.Black
            e.Graphics.DrawString(text, Me.Font, b, e.Bounds, StringFormat.GenericDefault)

            ' draw a light grey border line to separate the items
            Dim p As New Pen(Brushes.Gainsboro, 1)
            e.Graphics.DrawLine(p, New Point(e.Bounds.Left, e.Bounds.Bottom - 1), New Point(e.Bounds.Right, e.Bounds.Bottom - 1))
            p.Dispose()

            e.DrawFocusRectangle()
        End Sub
    End Class
End Namespace