1

Using Delphi 10.3:

In an owner-drawn TComboBox with Style=csOwnerDrawFixed, I want the owner drawn items in the DropDown list to be different from the static part of the combo. To discriminate between the two cases, I check for odComboBoxEdit in the State parameter, as described here:

How to draw the static part of the combobox

procedure TStylePanel.TargetArrowComboDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
begin
  if (odComboBoxEdit in State) then
  begin
    // Paint static control
  end
  else
  begin
    // Paint item in dropped down list
  end;
end;

This works well as long as there's no custom VCL style active. However, with a custom style, this no longer works reliably. Checking the source in Vcl.StdCtrls.pas for TComboBoxStyleHook, it seems to me that the cause is in this combination:

procedure TComboBoxStyleHook.WMPaint(...)
procedure TComboBoxStyleHook.DrawItem(...)

When there's no edit handle (which is the case for csOwnerDrawFixed), DrawItem() assembles a TDrawItemStruct that will never contain ODS_COMBOBOXEDIT, as a result the CN_DRAWITEM handler will never have odComboBoxEdit set.

I could override TComboBoxStyleHook, but I'd need a way to detect if the item is the static item or an item in the list.

As a workaround, I check for Combo.DroppedDown, but that's not the same: even when dropped down, I want the static part to be painted differently than the items in the list.

So the question is, how can I detect (in the custom draw handler or in the style hook) that the custom drawn item is the static area rather than an item in the list?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Gerrit Beuze
  • 903
  • 2
  • 10
  • 26

2 Answers2

2

I was able to get it working by adding a stylehook for TComboBox that unconditionally includes ODS_COMBOBOXEDIT. The assumption is that TComboBoxStyleHook.DrawItem is only called by TComboBoxStyleHook.WMPaint when it needs to custom draw the static item, the drop down list is not handled there. There seem to be no unwanted side effects.

type
  TComboBoxStyleHookFix = class(TComboBoxStyleHook)
  strict protected
    procedure DrawItem(Canvas: TCanvas; Index: Integer; const R: TRect; Selected: Boolean); override;
  end;

procedure TComboBoxStyleHookFix.DrawItem(Canvas: TCanvas; Index: Integer; const R: TRect; Selected: Boolean);
var
  DIS: TDrawItemStruct;
begin
  FillChar(DIS, SizeOf(DIS), 0);
  DIS.CtlType := ODT_COMBOBOX;
  DIS.CtlID := GetDlgCtrlID(Handle);
  DIS.itemAction := ODA_DRAWENTIRE;
  DIS.hDC := Canvas.Handle;
  DIS.hwndItem := Handle;
  DIS.rcItem := R;
  DIS.itemID := Index;
  DIS.itemData := SendMessage(ListHandle, LB_GETITEMDATA, 0, 0);
  if (Control is TComboBox) and (TComboBox(Control).Style = csOwnerDrawFixed) then
    DIS.itemState := ODS_COMBOBOXEDIT;
  if Selected then
    DIS.itemState := DIS.itemState or ODS_FOCUS or ODS_SELECTED;

  SendMessage(Handle, WM_DRAWITEM, Handle, LPARAM(@DIS));
end;

procedure InitComboStyleHookFix();
begin
  TCustomStyleEngine.RegisterStyleHook(TComboBox, TComboBoxStyleHookFix);
end;
Gerrit Beuze
  • 903
  • 2
  • 10
  • 26
0

It seems that in the edit control, TCustomCombobox does all the work in

procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;

It never calls WM_DRAWITEM, so one solution is to override that method (CnDrawItem).