2012-02-06 11 views
5

ComboBox di Windows ordinario (stile csDropDown o csDropDownList) aprirà il suo elenco a discesa subito sotto o, se non vi è spazio disponibile in basso, sopra la combo. Posso controllare la posizione di questo elenco (almeno per la coordinata Y)?Posso impostare a livello di codice la posizione dell'elenco a discesa ComboBox?

+6

Basta chiedersi: perché? Cosa nel comportamento predefinito non è di tuo gradimento? –

+0

@ MarjanVenema Il nostro progettista desidera migliorare l'usabilità per la casella combinata owner-draw – Andrew

risposta

10

pubblicazione di un esempio di codice che mostra la lista di animazione discesa correttamente e costringerà che mostra l'elenco a discesa sopra ComboBox1. questo codice sottoclassi 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; 

Note:

  1. Sono totalmente d'accordo con David, e altri che questa è una cattiva idea quella di modificare questo comportamento predefinito specifico per TComboBox. OP non ha ancora risposto a perché voleva tale comportamento.
  2. Il codice sopra è stato testato con D5/XP.
+0

Testato con successo, grazie! – Andrew

4

Bene, è possibile farlo utilizzando GetComboBoxInfo per ottenere un handle per la finestra utilizzata per l'elenco e quindi spostare quella finestra. Come questo:

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; 

ho ignorato la questione del controllo degli errori per mantenere il codice semplice.

Tuttavia, si tenga presente che sembra piuttosto orribile perché l'animazione a discesa è ancora visualizzata. Forse puoi trovare un modo per disabilitarlo.

Tuttavia, non è necessario fare nulla di simile perché Windows lo fa già per te. Trascina un modulo nella parte inferiore dello schermo e trascina verso il basso la tua combo. Quindi vedrai apparire la lista sopra la combo. Come questo:

enter image description here

+3

Testata in XP con D5. questo codice non funziona per me. il 'cbi.hwndList' non viene spostato. si apre e si chiude immediatamente. – kobik

+1

@kobik Un altro motivo per non farlo. Mi aspetto che il problema sia con XP piuttosto che con D5. Probabilmente è necessario cambiare il comportamento per diverse versioni del sistema operativo. Mai un buon piano. –

+2

Sono d'accordo al 100%. questo potrebbe probabilmente essere fatto agganciandolo a 'GWL_WNDPROC' e gestendo' WM_SIZE', ma il comportamento è così inaspettato che vorrei scaricare completamente questa idea. solo un commento a parte, penso che l'uso di 'GetComboBoxInfo' sia migliore di CB_GETCOMBOBOXINFO (vedi il commento relativo agli arresti anomali su msdn). – kobik

Problemi correlati