The Combo Box Windows control (aka TComboBox in Delphi) is one of the most frequently used user interface elements along with buttons and edits in Windows applications. TComboBox control represents an edit box with a scrollable drop-down list attached to it. Users can select an item from the list or type directly into the edit box. When the Style property is set to csDropDownList the combo allows to display a list of predefined items a user can select from the combo’s drop down list.
I’ve had a request from a user of my application: can the selection list be dropped down and then closed if no selection has been made in some period of time? Well, the user is asking. So, certainly it can!
The task to drop combo’s list is super easy: just set DroppedDown property to true (this sends CB_SHOWDROPDOWN to the combo). Then wait – but do not block the main thread !! – so some multithreading needed. Ok, use Delphi’s PPL. Finally, once the wait period is over, close the combo’s list – but, let’s say, only if the user is not hovering the mouse over combo’s items!
Looks pretty straightforward.
The only part to figure out is how to know if the mouse is over the list. Luckily, there’s a super handy Windows API call: GetComboBoxInfo. This function fills in the tagCOMBOBOXINFO structure (TComboBoxInfo record in Delphi’s implementation) with handles to the combo’s list box, edit box and the combo box itself. Therefore: get the mouse coordinates, and if over combo’s list (or combo itself) – simply leave it open.
Threaded Approach to Dropping Down and Closing Combo’s List
And here’s the resulting procedure: ComboDropAndCloseDelayed. Feed it with a TComboBox instance and set the needed delay time. Combo’s list will show, time will pass, and if the user is not over the combo’s list with the mouse (so still deciding of the selection) – close the list.
EDIT: Warning: the wanted quick and dirty approach is not the way to go. I “forgot” that TWinControl.Handle is not thread safe. Please read Remy’s comment….
//how not to do it :) procedure ComboDropAndCloseDelayed(const theCombo : TComboBox; const closeDelayMS : Cardinal = 2500); begin TTask.Run( procedure var cbi : TComboBoxInfo; begin //No-no! TWinControl.Handle is *not* thread safe! cbi.cbSize := SizeOf(TComboBoxInfo); if NOT GetComboBoxInfo(theCombo.Handle, cbi) then Exit; TThread.Queue(nil, procedure begin theCombo.DroppedDown := true; end); Sleep(closeDelayMS); TThread.Queue(nil, procedure var undrop : boolean; hw : THandle; begin undrop := true; if theCombo.HandleAllocated AND theCombo.DroppedDown then begin hw := WindowFromPoint(Mouse.CursorPos); undrop := (hw <> 0) AND (hw <> cbi.hwndCombo) AND (hw <> cbi.hwndList) AND (hw <> cbi.hwndItem); if undrop then theCombo.DroppedDown := false; end; end); end); end;
And how to use (example):
procedure TMainForm.Button1Click(Sender: TObject); begin //if no selection already: drop the list if ComboBox1.ItemIndex = -1 then ComboDropAndCloseDelayed(ComboBox1); end;
That’s it. Nice and quick and neat …