2009-08-07 5 views
5

nicht einstellen Ich kann die DropDownHeight der ComboBox nicht richtig einstellen, um alle Elemente anzuzeigen.Konnte die DropDownHeight von ComboBox

Ich verwende ein Steuerelement, das von der ComboBox erbt. Ich habe die Methoden OnDrawItem und OnMeasureItem überschrieben, um mehrere Spalten und Textumbruch innerhalb einer Spalte zu erstellen, wenn dies erforderlich ist. Das alles funktioniert gut.

Das Problem tritt auf, wenn ich versuche, DropDownHeight festzulegen. Ich habe die DropDownHeight auf einen beliebig großen Wert gesetzt, ein gutes Stück größer als die Liste der Elemente. Das ComboBox-Steuerelement scheint automatisch einen Wert für DropDownHeight abzuschneiden, der größer als die Größe aller angezeigten Elemente in der Liste ist. (Vorausgesetzt, dass Sie die MaxDropDownItems Eigenschaft haben, höher als die Anzahl der Elemente, die ich tue.) Normalerweise funktioniert dieses Verhalten perfekt, wie unten dargestellt: alt text http://www.freeimagehosting.net/uploads/dd09404697.png

Nein, das ist nicht meine wirklichen Daten in der Dropdown-Box .

Das Problem tritt auf, wenn ich einen Eintrag in der Dropdown-Liste habe, die umbrochen werden muss, um den vollständigen Text anzuzeigen. Dieser Eintrag wird zwar angezeigt, aber die ComboBox berechnet die DropDownHeight. Sie ignoriert, dass einer der Einträge doppelt so groß wie normal ist. Sie müssen daher eine Zeile nach unten scrollen, um zum letzten Eintrag in der Dropdownliste zu gelangen. alt text http://www.freeimagehosting.net/uploads/d0ef715f83.png

Dies ist der Code, den ich, wenn ein Artikel Textumbruch, um zu bestimmen bin mit benötigt und der Höhe der einzelnen Elemente setzen:

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 

ich nicht bestimmen kann, wie die DropDownHeight Eigenschaft zu zwingen, genau zu sein, der Wert, den ich möchte, oder wie das ComboBox-Steuerelement weiß, dass eines (oder mehrere) der Elemente in der Liste größer als normal sind.

Ich habe versucht, Override Schatten die DropDownHeight-Eigenschaft, aber das schien keine Auswirkungen zu haben.

EDIT:
Würde zu WPF Schalt dieses Problem weggehen? (Gibt es genug Anpassbarkeit in der Standard-WPF-Steuerelemente so, dass ich keine eigene Steuerung für eine 3-Spalten mit variabler Höhe Combobox zu schreiben?)

Antwort

9

Ich versuche genau dieses selbe Problem im Moment für eine Anwendung zu lösen, die ich von VB6 zu VB.NET migriere. Das vom Besitzer gezeichnete Combo-Steuerelement, das ich in VB6 habe, legt die Höhe des Dropdown-Menüs über einen SetWindowPos-API-Aufruf als Antwort auf die WM_CTLCOLORLISTBOX-Nachricht im Kombinationssteuerelement fest, wodurch wir Zugriff auf das HWnd für die Dropdownliste des Kombinationsfelds erhalten Steuerung. Der folgende Code wurde zu meiner Klasse hinzugefügt, die von ComboBox erbt und scheint, den Trick zu tun, aber muss noch getestet werden. Ich bin mir nicht sicher, ob das auch der eleganteste Weg ist. Offensichtlich müssen Sie die Zeile ändern, die die newHeight-Variable festlegt, aber das sollte Ihnen die allgemeine Idee geben.

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 
+0

JDHnz, danke für deine Antwort. Ich versuche, die Windows-Nachrichten nicht zu kapern, aber Ihre Lösung sieht so aus, als könnte sie für mich funktionieren, wenn ich keinen anderen Weg finde. Ich müsste meinem Steuerelement zusätzliche Funktionalität hinzufügen, die ItemHeight für jedes Element in der Combobox speichert, aber das sollte nicht zu schwer sein. – Stewbob

+0

Danke JDHnz. Ich konnte dies erfolgreich in meine Anwendung implementieren. – Stewbob

0

Try MyBase.OnMeasureItem am Ende des Verfahrens zu nennen

+1

Ich habe versucht, MyBase.OnMeasureItem vor meinem Code, danach, und sogar ganz verlassen. Alles ohne Wirkung. Danke für die Antwort. Ich fing an zu denken, dass ich mit einem Tumbleweed hier enden würde. – Stewbob

0

Edit: Ich habe gerade versucht, das Problem zu reproduzieren, aber alles funktioniert:

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

Wenn ich mich richtig erinnere Sie die Eigenschaft DrawMode zu OwnerDrawVariable setzen müssen, damit Zeichnen von benutzerdefinierten Artikelhöhen Wenn Sie dies tun, müssen Sie auch das Ereignis DrawItem behandeln. Werfen Sie einen Blick auf die Immobilienhilfe in MSDN.

+0

Ich setze den DrawMode im Konstruktor auf OwnerDrawVariable und überschreibe das DrawItem-Ereignis. So erhalte ich das mehrspaltige Display, die wechselnden Hintergrundfarben und den Textumbruch im Drop-Down-Menü. – Stewbob

2

Hier ist die C# Version der angenommenen Antwort.

[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); 
    } 
Verwandte Themen