我无法正确设置ComboBox的DropDownHeight以显示所有项目。
我正在使用一个继承自ComboBox的控件。我已经重写了OnDrawItem和OnMeasureItem方法,以便在需要时在列中创建多个列和文本换行。一切正常。
当我尝试设置DropDownHeight时会出现问题。我将DropDownHeight设置为一个任意大的值,比项目列表大一点。 ComboBox控件似乎自动截断DropDownHeight的任何值,该值大于列表中所有显示项的大小。 (假设您将MaxDropDownItems属性设置为高于项目数,我这样做。)通常这种行为完美无缺,如下所示: alt text http://www.freeimagehosting.net/uploads/dd09404697.png
不,这不是我在下拉框中的真实数据。
当我在下拉列表中有一个需要换行的条目以显示全文时,会出现问题。此条目显示正常,但是ComboBox正在计算DropDownHeight,它忽略了其中一个条目是正常值的两倍这一事实,因此您必须向下滚动一行才能到达下拉列表中的最后一个条目。 alt text http://www.freeimagehosting.net/uploads/d0ef715f83.png
这是我用来确定某个项目是否需要文本换行以及设置每个项目的高度的代码:
Protected Overrides Sub OnMeasureItem(ByVal e As System.Windows.Forms.MeasureItemEventArgs)
MyBase.OnMeasureItem(e)
//Determine the proper height of the current row in the dropdown based on
//the length of the OptionDescription string.
Dim tmpStr As String = FilterItemOnProperty(Items(e.Index), "OptionDescription")
Dim lng As Single = e.Graphics.MeasureString(tmpStr, Me.Font).Width
//Use the length of the item and the width of the column to calculate if wrapping is needed.
Dim HeightMultiplier As Integer = Math.Floor(lng / _ColumnWidths(1)) + 1
e.ItemHeight = e.ItemHeight * HeightMultiplier
End Sub
我无法确定如何强制DropDownHeight属性完全是我想要的值,或者如何让ComboBox控件知道列表中的一个(或多个)项目高于正常值。
我已经尝试覆盖影子DropDownHeight属性,但这似乎没有影响。
修改
切换到WPF会让这个问题消失吗? (标准WPF控件中是否有足够的可自定义性,因此我不需要为3列可变高度组合框编写自定义控件?)
答案 0 :(得分:9)
我正试图在我正在从VB6迁移到VB.NET的应用程序中解决这个完全相同的问题。我在VB6中拥有的所有者绘制的组合控件通过SetWindowPos API调用设置下拉的高度,以响应组合控件上的WM_CTLCOLORLISTBOX消息,这使我们可以访问组合下拉列表的HWnd控制。我的类中添加了以下代码,它继承自ComboBox,似乎可以解决问题,但仍需要测试。我不确定这也是最优雅的方式。显然你需要更改设置newHeight变量的行,但这应该给你一般的想法。
Private 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
Private Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Integer, ByRef lpRect As RECT) As Integer
Private Declare Sub SetWindowPos Lib "user32" _
(ByVal hwnd As Integer, ByVal hWndInsertAfter As Integer, _
ByVal X As Integer, ByVal Y As Integer, _
ByVal cx As Integer, ByVal cy As Integer, _
ByVal wFlags As Integer)
Private Const SWP_NOZORDER As Integer = &H4
Private Const SWP_NOACTIVATE As Integer = &H10
Private Const SWP_FRAMECHANGED As Integer = &H20
Private Const SWP_NOOWNERZORDER As Integer = &H200
Private _hwndDropDown As Integer = 0
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_CTLCOLORLISTBOX As Integer = &H134
If m.Msg = WM_CTLCOLORLISTBOX Then
If _hwndDropDown = 0 Then
_hwndDropDown = m.LParam.ToInt32
Dim r As RECT
GetWindowRect(m.LParam.ToInt32, r)
'height of four items plus 2 pixels for the border in my test
Dim newHeight As Integer = 4 * MyBase.ItemHeight + 2
SetWindowPos(m.LParam.ToInt32, 0, _
r.Left, _
r.Top, _
MyBase.DropDownWidth, _
newHeight, _
SWP_FRAMECHANGED Or _
SWP_NOACTIVATE Or _
SWP_NOZORDER Or _
SWP_NOOWNERZORDER)
End If
End If
MyBase.WndProc(m)
End Sub
Protected Overrides Sub OnDropDownClosed(ByVal e As System.EventArgs)
_hwndDropDown = 0
MyBase.OnDropDownClosed(e)
End Sub
答案 1 :(得分:2)
以下是接受答案的c#版本。
[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);
//height of four items plus 2 pixels for the border in my test
int newHeight;
if (Items.Count <= MaxDropDownItems)
{
newHeight = Items.Count * ItemHeight + 2;
}
else
{
newHeight = MaxDropDownItems * ItemHeight + 2;
}
SetWindowPos((IntPtr)_hwndDropDown, IntPtr.Zero,
r.Left,
r.Top,
DropDownWidth,
newHeight,
SWP_FRAMECHANGED |
SWP_NOACTIVATE |
SWP_NOZORDER |
SWP_NOOWNERZORDER);
}
}
base.WndProc(ref m);
}
protected override void OnDropDownClosed(EventArgs e)
{
_hwndDropDown = 0;
base.OnDropDownClosed(e);
}
答案 2 :(得分:0)
尝试在方法结束时调用MyBase.OnMeasureItem
答案 3 :(得分:0)
修改强> 我只是试图重现你的问题,但一切正常:
class MyCustomComboBox : ComboBox
{
public MyCustomComboBox()
{
DrawMode = DrawMode.OwnerDrawVariable;
DropDownHeight = 255;
DropDownWidth = 300;
MaxDropDownItems = 20;
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
base.OnMeasureItem(e);
if (e.Index % 2 == 0)
e.ItemHeight = ItemHeight * 3;
else
e.ItemHeight = ItemHeight * 2;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
// Draw the background of the item.
e.DrawBackground();
Rectangle rectangle = new Rectangle(2, e.Bounds.Top + 2,
e.Bounds.Height, e.Bounds.Height - 4);
e.Graphics.FillRectangle(new SolidBrush(Color.Gray), rectangle);
Font myFont = new Font(FontFamily.GenericSansSerif, 30, FontStyle.Bold);
e.Graphics.DrawString(this.Items[e.Index] as string, myFont, Brushes.Black,
new RectangleF(e.Bounds.X + rectangle.Width, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
// Draw the focus rectangle if the mouse hovers over an item.
e.DrawFocusRectangle();
}
}
如果我没记错的话,您必须将属性 DrawMode 设置为 OwnerDrawVariable 以启用绘制自定义项目高度。如果你这样做,你还必须处理 DrawItem 事件。 请查看MSDN中的财产帮助。