Can I programmatically set the position of ComboBox dropdown list?
Asked Answered
I

3

5

Ordinary Windows ComboBox (csDropDown or csDropDownList style) will open its dropdown list right below or, if no space left below, above the combo. Can I control the position of this list (at least by Y coordinate)?

Idea answered 6/2, 2012 at 14:8 Comment(2)
Just wondering: why? What in the default behaviour is not to your liking?Dyad
@MarjanVenema Our designer want to make some usability improvement for owner-draw comboboxIdea
H
10

Posting a code example that will show drop-down list animation correctly and will force showing the drop-down list above ComboBox1. this code subclasses 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. I totally agree with David, and others that this is a bad idea to change this specific default behavior for TComboBox. OP did not yet respond to why he wanted such behavior.
  2. The code above was tested with D5/XP.
Homothallic answered 6/2, 2012 at 19:0 Comment(0)
O
4

Well, you can do this by using GetComboBoxInfo to obtain a handle to the window used for the list, and then move that window. Like this:

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;

I have ignored the issue of error checking to keep the code simple.

However, be warned that it looks pretty horrible because the dropdown animation is still shown. Perhaps you can find a way to disable that.

However, you simply do not need to do anything like this because Windows already does it for you. Drag a form to the bottom of the screen and drop down your combo. Then you will see the list appear above the combo. Like this:

enter image description here

Oldster answered 6/2, 2012 at 15:13 Comment(4)
Tested in XP with D5. this code is not working for me. the cbi.hwndList is not being moved. it opens and closes immediately.Homothallic
@Homothallic Yet another reason not to do this. I expect the issue is with XP rather than D5. You probably need to switch behaviour for different OS versions. Never a good plan.Oldster
I agree 100%. this could probably be done by hooking to GWL_WNDPROC and handling WM_SIZE, but the behavior is so unexpected that I would totally dump this idea. just a side comment, I think using GetComboBoxInfo is better than CB_GETCOMBOBOXINFO (see comment regarding crashes on msdn).Homothallic
@Homothallic Thanks, I've switched it to GetComboBoxInfoOldster
H
-1

this works for me to handle the size of the DropDownList in case the style of the Combobox is set to csOwnerDrawVariable!

Without the height is much too small and cannot be set by DropDownCount as expected!

(Delphi 11.3 Patch 1)

Thanks

Hawkie answered 7/7, 2023 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.