【问题标题】:Allow each item to use multiple lines in a Winforms Combobox (or Listbox)允许每个项目在 Winforms 组合框(或列表框)中使用多行
【发布时间】:2010-10-22 22:36:19
【问题描述】:

这可以相对容易地完成吗?

【问题讨论】:

    标签: winforms


    【解决方案1】:

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

    以下是我对这个问题的看法(您可以查看另一个示例,在项目here 中绘制图标)。

    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++;
            }            
        }             
    }
    

    用法:创建一个常规表单并在其上放置一个组合框。

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

    【讨论】:

      【解决方案2】:

      我发现 Tim Mackey 制作的这个课程在我的项目中效果很好 (Tim's Blog Entry):

      C#版本:

      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();
              }
          }
      }
      

      VB版本:

      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
      

      【讨论】:

      • 我想知道为什么这个解决方案将 ComboBox 子类化并处理 DrawItem 事件?为什么有必要?他似乎正在根据额外的高度调整窗口的位置,但我看不到其他解决方案需要这个
      • 仅供参考,设置 DropDownHeight 的行可能会导致组合框在打开之前需要多次点击。只需将结果存储在局部变量中并在调用 SetWindowPos 时使用它。它仍然有效,但不会弄乱投递箱的打开方式。
      【解决方案3】:

      这个问题有点老了,但我发现带有RectangleF 参数的DrawString 不仅将文本剪辑到该矩形,而且还包裹它。

      示例代码:

      StringFormat sf = StringFormat.GenericTypographic;
      sf.Trimming = StringTrimming.EllipsisCharacter;
      g.DrawString(text, font, foregroundBrush, e.Bounds, sf);
      

      另见MS Docs: How to: Display Side-Aligned Tabs with TabControl

      【讨论】:

        【解决方案4】:

        我增强了 Steinar 的答案,它给了我一个很棒的组合框,可以容纳可变的多行文本,包括列表中每个项目的工具提示。

        这里是做什么:

        为组合框设置 DrawItem、MeasureItem 事件如下。还可以使用 Wrapstring() 方法生成多行的每个块来计算每行的行数和高度:

        // Goes in Form load / constructor
            cmbMulti.DrawMode = DrawMode.OwnerDrawVariable;
            cmbMulti.DrawItem += new DrawItemEventHandler(cmbMulti_DrawItem);
            cmbMulti.MeasureItem += new MeasureItemEventHandler(cmbMulti_MeasureItem);
            cmbMulti.Items.Add("Multiline Text 1");
            cmbMulti.Items.Add("Multiline Text 2");
            cmbMulti.Items.Add("Multiline Text ");
            ToolTip tooltip = new ToolTip();
            
            // Set dropdown height
                
            int totalLines = 0, stringHeight = 0;
                        foreach (string item in cmbMulti.Items)
                        {
                            foreach (string str in WrapString(item, cmbMulti.CreateGraphics(), cmbMulti.Font, cmbMulti.Width))
                            {
                                totalLines++;
                                if (totalLines == 1)
                                {
                                    stringHeight = (int)cmbMulti.CreateGraphics().MeasureString(item, cmbMulti.Font).Height;
                                }
                            }
            
                        }
                        cmbMulti.DropDownHeight = TotalDropdownHeight + 2;
            
        
            
            private void cmbMulti_DrawItem(object sender, DrawItemEventArgs e)
                {
                    if (e.Index > -1)
                    {
                        string itemText = cmbMulti.Items[e.Index].ToString();
                        Brush backgroundBrush, forgroundBrush;
            
                        if (e.State == (DrawItemState.Selected | DrawItemState.NoAccelerator | DrawItemState.NoFocusRect) ||
                        e.State == DrawItemState.Selected)
                        {
                            forgroundBrush = new SolidBrush(SystemColors.Highlight);
                            backgroundBrush = new SolidBrush(SystemColors.Window);
                            this.tooltip.Show(itemText, cmbMulti, e.Bounds.Right, e.Bounds.Bottom);
                        }
                        else
                        {
                            forgroundBrush = new SolidBrush(SystemColors.Window);
                            backgroundBrush = Brushes.Black;
                            this.tooltip.Hide(cmbMulti);
                        }
            
                        if ((e.State & DrawItemState.Focus) == 0)
                        {
                            e.Graphics.FillRectangle(forgroundBrush, e.Bounds);
                            e.Graphics.DrawString(itemText, Font, backgroundBrush,
                                e.Bounds);
                            e.Graphics.DrawRectangle(new Pen(SystemColors.Highlight), e.Bounds);
                        }
                        else
                        {
                            e.Graphics.FillRectangle(forgroundBrush, e.Bounds);
                            e.Graphics.DrawString(itemText, Font, backgroundBrush,
                                e.Bounds);
                        }
                    }
                }
                int TotalDropdownHeight = 0;
                protected void cmbMulti_MeasureItem(object sender, MeasureItemEventArgs e)
                {
                    if (e.Index > -1)
                    {
                        string itemText = cmbMulti.Items[e.Index].ToString();
                        SizeF sf = e.Graphics.MeasureString(itemText, Font, cmbMulti.Width);
                        int multiLineCount = 0;
                        foreach (string item in WrapString(itemText, e.Graphics, cmbMulti.Font, cmbMulti.Width))
                        {
                            multiLineCount++;
                        }
                        e.ItemHeight = multiLineCount * 15;
                        TotalDropdownHeight += e.ItemHeight;
                        e.ItemWidth = cmbMulti.Width;
                    }
                }
                IEnumerable<string> WrapString(string str, Graphics g, Font font,
                                                   int allowedWidth)
                        {
                            string[] arr = str.Split(' ');
                            StringBuilder current = new StringBuilder();
                            foreach (string token in arr)
                            {
                                int width =
                                  (int)g.MeasureString(current.ToString() + " " + token, font).Width;
                                if (width > allowedWidth)
                                {
                                    yield return current.ToString();
                                    current = new StringBuilder();
                                }
                                current.Append(token + " ");
                            }
                            yield return current.ToString();
                        }
        

        【讨论】:

          猜你喜欢
          • 2013-03-14
          • 2019-01-20
          • 1970-01-01
          • 2021-10-03
          • 1970-01-01
          • 2012-11-19
          • 1970-01-01
          • 2021-06-26
          • 1970-01-01
          相关资源
          最近更新 更多