2012-02-06 4 views
5

Ordinaire Windows ComboBox (csDropDown ou csDropDownList style) ouvrira sa liste déroulante juste en dessous ou, s'il n'y a pas d'espace ci-dessous, au-dessus du combo. Puis-je contrôler la position de cette liste (au moins par coordonnée Y)?Puis-je programmer la position de la liste déroulante ComboBox?

+6

Je me demandais: pourquoi? Qu'est-ce que dans le comportement par défaut n'est pas à votre goût? –

+0

@MarjanVenema Notre concepteur veut faire de l'amélioration de l'utilisabilité pour le combobox owner-draw – Andrew

Répondre

10

L'affichage d'un exemple de code qui correctement et forcera l'animation afficher la liste déroulante qui affiche la liste déroulante ci-dessus ComboBox1. ce code sous-classe ComboBox hwndList:

TForm1 = class(TForm) 
    ComboBox1: TComboBox; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
private 
    FComboBoxListDropDown: Boolean; 
    FComboBoxListWnd: HWND; 
    FOldComboBoxListWndProc, FNewComboBoxListWndProc: Pointer; 
    procedure ComboBoxListWndProc(var Message: TMessage); 
end; 

.... 

procedure TForm1.FormCreate(Sender: TObject); 
var 
    Info: TComboBoxInfo; 
begin 
    ZeroMemory(@Info, SizeOf(Info)); 
    Info.cbSize := SizeOf(Info); 
    GetComboBoxInfo(ComboBox1.Handle, Info); 
    FComboBoxListWnd := Info.hwndList; 
    FNewComboBoxListWndProc := MakeObjectInstance(ComboBoxListWndProc); 
    FOldComboBoxListWndProc := Pointer(GetWindowLong(FComboBoxListWnd, GWL_WNDPROC)); 
    SetWindowLong(FComboBoxListWnd, GWL_WNDPROC, Integer(FNewComboBoxListWndProc)); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    SetWindowLong(FComboBoxListWnd, GWL_WNDPROC, Integer(FOldComboBoxListWndProc)); 
    FreeObjectInstance(FNewComboBoxListWndProc); 
end; 

procedure TForm1.ComboBoxListWndProc(var Message: TMessage); 
var 
    R: TRect; 
    DY: Integer; 
begin 
    if (Message.Msg = WM_MOVE) and not FComboBoxListDropDown then 
    begin 
    FComboBoxListDropDown := True; 
    try 
     GetWindowRect(FComboBoxListWnd, R); 
     DY := (R.Bottom - R.Top) + ComboBox1.Height + 1; 
     // set new Y position for drop-down list: always above ComboBox1 
     SetWindowPos(FComboBoxListWnd, 0, R.Left, R.Top - DY , 0, 0, 
     SWP_NOOWNERZORDER or SWP_NOZORDER or SWP_NOSIZE or SWP_NOSENDCHANGING); 
    finally 
     FComboBoxListDropDown := False; 
    end; 
    end; 
    Message.Result := CallWindowProc(FOldComboBoxListWndProc, 
    FComboBoxListWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 

Notes:

  1. Je suis totalement d'accord avec David, et d'autres que cela est une mauvaise idée de changer ce comportement par défaut spécifique pour TComboBox. OP n'a pas encore répondu à pourquoi il voulait un tel comportement.
  2. Le code ci-dessus a été testé avec D5/XP.
+0

Testé avec succès, merci! – Andrew

4

Eh bien, vous pouvez le faire en utilisant GetComboBoxInfo pour obtenir un handle à la fenêtre utilisée pour la liste, puis déplacez cette fenêtre. Comme ceci:

type 
    TMyForm = class(TForm) 
    ComboBox1: TComboBox; 
    procedure ComboBox1DropDown(Sender: TObject); 
    protected 
    procedure WMMoveListWindow(var Message: TMessage); message WM_MOVELISTWINDOW; 
    end; 

.... 

procedure TMyForm.ComboBox1DropDown(Sender: TObject); 
begin 
    PostMessage(Handle, WM_MOVELISTWINDOW, 0, 0); 
end; 

procedure TMyForm.WMMoveListWindow(var Message: TMessage); 
var 
    cbi: TComboBoxInfo; 
    Rect: TRect; 
    NewTop: Integer; 
begin 
    cbi.cbSize := SizeOf(cbi); 
    GetComboBoxInfo(ComboBox1.Handle, cbi); 
    GetWindowRect(cbi.hwndList, Rect); 
    NewTop := ClientToScreen(Point(0, ComboBox1.Top-Rect.Height)).Y; 
    MoveWindow(cbi.hwndList, Rect.Left, NewTop, Rect.Width, Rect.Height, True); 
end; 

J'ai ignoré le problème de la vérification des erreurs pour garder le code simple. Cependant, sachez que cela semble assez horrible parce que l'animation dropdown est toujours affichée. Peut-être que vous pouvez trouver un moyen de désactiver cela. Toutefois, vous n'avez simplement pas besoin de faire quoi que ce soit de ce genre, car Windows le fait déjà pour vous. Faites glisser un formulaire en bas de l'écran et faites glisser votre combo. Ensuite, vous verrez la liste apparaître au-dessus du combo. Comme ceci:

enter image description here

+3

Testé en XP avec D5. Ce code ne fonctionne pas pour moi. le 'cbi.hwndList' n'est pas déplacé. il s'ouvre et se ferme immédiatement. – kobik

+1

@kobik Encore une autre raison de ne pas le faire. Je pense que le problème est avec XP plutôt que D5. Vous devez probablement changer de comportement pour différentes versions de système d'exploitation. Jamais un bon plan. –

+2

Je suis d'accord à 100%. cela pourrait probablement être fait en se connectant à 'GWL_WNDPROC' et en manipulant' WM_SIZE', mais le comportement est si inattendu que je voudrais totalement vider cette idée. juste un commentaire de côté, je pense que l'utilisation de 'GetComboBoxInfo' est meilleure que CB_GETCOMBOBOXINFO (voir le commentaire concernant les plantages sur msdn). – kobik

Questions connexes