2009-08-07 7 views
5

Je ne parviens pas à afficher correctement le DropDownHeight du ComboBox pour afficher tous les éléments.Impossible de définir DropDownHeight de ComboBox

J'utilise un contrôle qui hérite du ComboBox. J'ai substitué les méthodes OnDrawItem et OnMeasureItem afin de créer plusieurs colonnes et l'habillage de texte dans une colonne si cela est nécessaire. Tout cela fonctionne très bien.

Le problème se produit lorsque j'essaie de définir DropDownHeight. J'ai mis le DropDownHeight à une valeur arbitrairement grande, un peu plus grande que la liste des éléments. Le contrôle ComboBox semble tronquer automatiquement toute valeur pour DropDownHeight qui est supérieure à la taille de tous les éléments affichés dans la liste. (En supposant que vous avez la propriété MaxDropDownItems réglée plus haut que le nombre d'articles que je fais.) Normalement, ce comportement fonctionne parfaitement, comme indiqué ci-dessous: alt text http://www.freeimagehosting.net/uploads/dd09404697.png

Non, ce n'est pas mes données réelles dans la liste déroulante .

Le problème se produit lorsque j'ai une entrée dans la liste déroulante qui doit envelopper pour afficher le texte intégral. Cette entrée s'affiche correctement, mais le composant ComboBox calcule le DropDownHeight. Il ignore le fait que l'une des entrées est deux fois plus grande que la normale. Vous devez donc faire défiler une ligne pour accéder à la dernière entrée de la liste déroulante. alt text http://www.freeimagehosting.net/uploads/d0ef715f83.png

Voici le code que j'utilise pour déterminer si un élément a besoin habillage de texte et de régler la hauteur de chaque élément:

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 

Je ne peux pas déterminer comment forcer la propriété DropDownHeight être exactement la valeur que je veux ou comment laisser le contrôle ComboBox savoir qu'un (ou plusieurs) des éléments de la liste sont plus grands que la normale.

J'ai essayé de Remplacer Ombre la propriété DropDownHeight, mais cela n'a apparemment pas eu d'impact.

EDIT:
Est-ce que le passage à ce problème WPF faire disparaître? (Y at-il assez customizability dans les contrôles standard WPF pour que je ne ai pas besoin d'écrire un contrôle personnalisé pour un 3-colonne, combobox hauteur variable?)

Répondre

9

J'essaie de résoudre moi-même exactement ce même problème en ce moment pour une application que je migre de VB6 à VB.NET. Le contrôle combo dessiné par le propriétaire que j'ai dans VB6 définit la hauteur de la liste déroulante via un appel API SetWindowPos en réponse au message WM_CTLCOLORLISTBOX sur le contrôle de liste déroulante, qui nous donne accès à HWnd pour la liste déroulante du combo contrôle. Le code suivant a été ajouté à ma classe qui hérite de ComboBox et semble faire l'affaire, mais doit encore être testé. Je ne suis pas sûr que ce soit la façon la plus élégante de le faire non plus. Évidemment, vous devrez changer la ligne qui définit la variable newHeight, mais cela devrait vous donner l'idée générale.

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, merci pour votre réponse. J'essaie d'éviter de détourner les messages Windows, mais votre solution semble fonctionner pour moi si je ne trouve pas d'autre moyen. Je devrais ajouter quelques fonctionnalités supplémentaires à mon contrôle qui stocke le ItemHeight pour chaque élément de la liste déroulante, mais cela ne devrait pas être trop difficile. – Stewbob

+0

Merci JDHnz. J'ai réussi à implémenter ceci dans mon application. – Stewbob

0

Essayez d'appeler MyBase.OnMeasureItem à la fin de la méthode

+1

J'ai essayé d'appeler MyBase.OnMeasureItem avant mon code, après celui-ci, et même en l'omettant complètement. Tous sans effet. Merci d'avoir répondu. Je commençais à penser que je finirais avec un tumbleweed sur celui-ci. – Stewbob

0

Edit: Je viens d'essayer de reproduire votre problème, mais tout fonctionne 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 je me souviens bien, vous devez définir la propriété DrawMode à OwnerDrawVariable pour permettre dessiner des hauteurs d'objets personnalisés. Si vous faites cela, vous devrez également gérer l'événement DrawItem. Jetez un oeil à l'aide de l'immobilier dans MSDN.

+0

Je définis le DrawMode sur OwnerDrawVariable dans le constructeur et remplace l'événement DrawItem. C'est ainsi que j'obtiens l'affichage multi-colonnes, les couleurs d'arrière-plan en alternance et l'habillage du texte dans la liste déroulante. – Stewbob

2

Voici la version C# de la réponse acceptée.

[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); 
    } 
Questions connexes