2009-08-07 15 views
5

No puedo obtener el DropDownHeight del ComboBox configurado correctamente para mostrar todos los elementos.No se puede establecer el DropDownHeight de ComboBox

Estoy usando un control que hereda del ComboBox. He reemplazado los métodos OnDrawItem y OnMeasureItem para crear varias columnas y ajuste de texto dentro de una columna si es necesario. Todo esto funciona bien

El problema se produce cuando intento establecer DropDownHeight. Establecí DropDownHeight en un valor arbitrariamente grande, un poco más grande que la lista de elementos. El control ComboBox parece truncar automáticamente cualquier valor para DropDownHeight que sea más grande que el tamaño de todos los elementos mostrados en la lista. (Asumiendo que usted tiene la propiedad MaxDropDownItems fija más alto que el número de elementos, lo que hago.) Normalmente este comportamiento funciona perfectamente, como se muestra a continuación: alt text http://www.freeimagehosting.net/uploads/dd09404697.png

No, eso no es mis datos reales en el cuadro desplegable .

El problema se produce cuando tengo una entrada en el menú desplegable que debe completarse para mostrar el texto completo. Esta entrada se muestra bien, pero sin embargo, el ComboBox está calculando DropDownHeight, ignora el hecho de que una de las entradas es dos veces más alta de lo normal, por lo que tiene que desplazarse hacia abajo una línea para llegar a la última entrada en el menú desplegable. alt text http://www.freeimagehosting.net/uploads/d0ef715f83.png

Este es el código que estoy usando para determinar si un artículo necesita ajuste de texto y para fijar la altura de cada elemento:

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 

no puedo determinar cómo forzar la propiedad DropDownHeight ser exactamente el valor que quiero, o cómo dejar que el control ComboBox sepa que uno (o más) de los elementos en la lista son más altos de lo normal.

He intentado Anular Sombrear la propiedad DropDownHeight, pero esto no parece tener ningún impacto.

EDIT:
¿Podría cambiar a WPF hacen que este problema desaparezca? (¿Hay suficiente capacidad de personalización en el estándar de WPF controles para que no necesito escribir un control personalizado de 3 columnas, cuadro combinado de altura variable?)

Respuesta

9

Estoy tratando de resolver exactamente este mismo problema en este momento para una aplicación que estoy migrando de VB6 a VB.NET. El control combo dibujado por el propietario que tengo en VB6 establece el alto del menú desplegable a través de una llamada API SetWindowPos en respuesta al mensaje WM_CTLCOLORLISTBOX en el control combinado, que nos da acceso al HWnd para la lista desplegable del combo controlar. El siguiente código fue agregado a mi clase que hereda de ComboBox y parece ser el truco, pero aún necesita ser probado. No estoy seguro de que sea la manera más elegante de hacer esto tampoco. Obviamente, deberá cambiar la línea que establece la nueva variable HeHight, pero esto debería darle la idea general.

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, gracias por su respuesta. Intento evitar secuestrar los mensajes de Windows, pero parece que tu solución podría funcionar si no puedo encontrar otra manera. Tendría que agregar alguna funcionalidad adicional a mi control que almacena el ItemHeight para cada elemento en el cuadro combinado, pero eso no debería ser demasiado difícil. – Stewbob

+0

Gracias JDHnz. Pude implementar esto con éxito en mi aplicación. – Stewbob

0

intenta llamar MyBase.OnMeasureItem al final del método

+1

He intentado llamar a MyBase.OnMeasureItem antes de mi código, después de él, e incluso lo he omitido por completo. Todo sin efecto. Gracias por responder sin embargo. Estaba empezando a pensar que terminaría con una planta rodadora en esta. – Stewbob

0

Editar: Sólo traté de reproducir su problema, pero todo funciona bien:

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

si no recuerdo mal hay que establecer el DrawMode propiedad a OwnerDrawVariable para habilitar dibujando alturas de artículos personalizados. Si lo hace, también tendrá que manejar el evento DrawItem. Eche un vistazo a la ayuda de la propiedad en MSDN.

+0

Establecí DrawMode en OwnerDrawVariable en el constructor y anulo el evento DrawItem. Así es como obtengo la visualización de múltiples columnas, los colores de fondo alternos y el ajuste del texto en el menú desplegable. – Stewbob

2

Aquí está la versión C# de la respuesta aceptada.

[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); 
    } 
Cuestiones relacionadas