这可以相对轻松地完成吗?
答案 0 :(得分: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++;
}
}
}
用法:创建一个常规表单并在其上放置一个组合框。
(请注意,这当然只是一个天真的概念证明 - 显然还有改进的余地。而且它只是假设只有两行而不是一行。但它表明这是可能的。)
答案 1 :(得分:1)
我发现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
答案 2 :(得分:0)
这个问题有点老了,但我发现带有 DrawString
参数的 RectangleF
不仅可以将文本剪切到该矩形,还可以将其换行。
示例代码:
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