【发布时间】:2010-10-22 22:36:19
【问题描述】:
这可以相对容易地完成吗?
【问题讨论】:
标签: winforms
这可以相对容易地完成吗?
【问题讨论】:
标签: winforms
我能够在 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++;
}
}
}
用法:创建一个常规表单并在其上放置一个组合框。
(请注意,这当然只是一个幼稚的概念证明——显然还有改进的余地。而且它只是假设只有两行而不是一行。但这表明这是可能的。)
【讨论】:
我发现 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
【讨论】:
这个问题有点老了,但我发现带有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
【讨论】:
我增强了 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();
}
【讨论】: