Delphi Delayed Close Programmatically Dropped TComboBox (If Mouse NOT Over Combo and Combo’s List) Using Multi Threading

FireWind

Свой
Регистрация
2 Дек 2005
Сообщения
1,957
Реакции
1,203
Credits
4,034
Delayed Close Programmatically Dropped TComboBox (If Mouse NOT Over Combo and Combo’s List) Using Multi Threading
Žarko Gajić

1615222368035.png
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 Для просмотра ссылки Войди или Зарегистрируйся. 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.
Код:
procedure ComboDropAndCloseDelayed(const theCombo : TComboBox; const closeDelayMS : Cardinal = 2500);
begin
  TTask.Run(
    procedure
    var
      cbi : TComboBoxInfo;
    begin
      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 …