12

How can I have TComboBox with some items that are disabled? I need the user to see these items, but not be able to select them.

Thanks!

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
croceldon
  • 4,511
  • 11
  • 57
  • 92
  • 1
    It's not evident from the question's title or its tags, but here is an implementation of a component derived from TComboBox with disabled item support: http://stackoverflow.com/questions/4356364 – Sertac Akyuz Apr 07 '11 at 17:07
  • 1
    Don't do this. It violates what users are expecting from comboboxes. Find another way. In any case, comboboxes are not a good way to present data to the user anyway; if you need them to seen an overview of what they can and cannot do, they should not have to perform an additional step (i.e. not have press a drop-down button). It's all about User Experience, don't mess with that! – Jeroen Wiert Pluimers Apr 08 '11 at 08:34

2 Answers2

19

Yes, and this is how to do it:

Drop a TComboBox on your form, and set Style to csOwnerDrawFixed. Then add the event handlers

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
const
  INDENT = 3;
begin
  with TComboBox(Control) do
  begin
    FillRect(Canvas.Handle, Rect, GetStockObject(WHITE_BRUSH));
    inc(Rect.Left, INDENT);
    if boolean(Items.Objects[Index]) then
      SetTextColor(Canvas.Handle, clBlack)
    else
      SetTextColor(Canvas.Handle, clGray);
    DrawText(Canvas.Handle,
      PChar(Items[Index]),
      length(Items[Index]),
      Rect,
      DT_SINGLELINE or DT_LEFT or DT_VCENTER or DT_END_ELLIPSIS)
  end;
end;

and

procedure TForm1.ComboBox1CloseUp(Sender: TObject);
begin
  with TComboBox(Sender) do
    if (ItemIndex <> -1) and not boolean(Items.Objects[ItemIndex]) then
    begin
      beep;
      Perform(CB_SHOWDROPDOWN, integer(true), 0);
    end;
end;

Also, in the interface section of your form, prior to the declaration of the form class, add

TComboBox = class(StdCtrls.TComboBox)
protected
  procedure WndProc(var Message: TMessage); override;
end;

and implement the WndProc as

procedure TComboBox.WndProc(var Message: TMessage);

  function NextItemIsDisabled: boolean;
  begin
    result := (ItemIndex < Items.Count - 1) and
      not boolean(Items.Objects[ItemIndex + 1]);
  end;

  procedure SelectNextEnabledItem;
  var
    i: Integer;
  begin
    for i := ItemIndex + 1 to Items.Count - 1 do
      if boolean(Items.Objects[i]) then
      begin
        ItemIndex := i;
        Exit;
      end;
    beep;
  end;

  procedure KillMessages;
  var
    msg: TMsg;
  begin
    while PeekMessage(msg,
      Handle,
      WM_KEYFIRST,
      WM_KEYLAST,
      PM_REMOVE) do;
  end;

  function PrevItemIsDisabled: boolean;
  begin
    result := (ItemIndex > 0) and
      not boolean(Items.Objects[ItemIndex - 1]);
  end;

  procedure SelectPrevEnabledItem;
  var
    i: Integer;
  begin
    for i := ItemIndex - 1 downto 0 do
      if boolean(Items.Objects[i]) then
      begin
        ItemIndex := i;
        Exit;
      end;
    beep;
  end;

begin
  case Message.Msg of
    WM_KEYDOWN:
      case Message.WParam of
        VK_DOWN:
          if NextItemIsDisabled then
          begin
            SelectNextEnabledItem;
            KillMessages;
            Exit;
          end;
        VK_UP:
          if PrevItemIsDisabled then
          begin
            SelectPrevEnabledItem;
            KillMessages;
            Exit;
          end;
      end;
  end;
  inherited;
end;

To test the combo box, write, for example

procedure TForm1.FormCreate(Sender: TObject);
begin
  ComboBox1.Items.AddObject('Alpha', TObject(true));
  ComboBox1.Items.AddObject('Beta', TObject(true));
  ComboBox1.Items.AddObject('Gamma', TObject(false));
  ComboBox1.Items.AddObject('Delta', TObject(true));
end;

I think you get the meaning of true and false here -- it simply means enabled.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • 6
    +1 for the effort, Andreas, although I'd shoot you if we worked together and you used this (and so would my users). They're used to comboboxes acting like comboboxes. I didn't post anything close to code; I figured if someone wanted to circumvent standards this way they should have to do some work to figure out coding it. – Ken White Apr 07 '11 at 19:55
  • @Ken: I would probably not use it either, but I have seen this type of non-standard comboboxes more and more the last years, especially on the Internet (HTML+CSS+JavaScript), so I think it is not *that* bad. – Andreas Rejbrand Apr 07 '11 at 20:21
  • But the desktop app is *Windows*, which has UI standards for this stuff. HTML/CSS/JS don't - you pretty much do what you want with them. (But I did give you the +1, even though I disagree - your answer was spot on for the original question.) :) – Ken White Apr 07 '11 at 20:26
  • +1 here also for the effort, but preventing close up after closing up brings some problems I think, click gamma and click outside or Esc, and you'll see. – Sertac Akyuz Apr 07 '11 at 20:58
7

It's not easy (and it's a bad idea, since that's not how comboboxes behave on Windows).

You'd have to owner draw the combobox yourself. Use the Items.Objects array to store whether or not the item is enabled or disabled, and check that array before drawing each item in order to set the colors appropriately.

You'd also need to handle the OnChange and OnClick events, and add a way to track the last selected ItemIndex. In OnChange/OnClick, you disconnect the event handler, check the Objects[ItemIndex] value to see if a selection is allowed, if not set the ItemIndex back to the last selected ItemIndex, and then re-enable the event handler.

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • 4
    What happens if the user clicks on the apparently disabled item in the dropped down combo box? The box will close and the user might not realize it reverted to the last selected value. That's confusing! – Cosmin Prund Apr 07 '11 at 15:54
  • You can find some additional information in the newsgroup here: http://groups.google.com/group/comp.lang.pascal.delphi.misc/browse_thread/thread/ea67221a7d3f7e13/17418f924fc64f43?lnk=raot&pli=1 – pritaeas Apr 07 '11 at 15:58
  • @Cosmin: Agreed. As I said, that's not how comboboxes work in windows. That's why I said it's a terrible idea, and yours was better. I also said it wasn't easy. – Ken White Apr 07 '11 at 16:16
  • @Cosmin You'd have to intercept the selection of a disabled item and not close the drop down. – David Heffernan Apr 07 '11 at 16:22
  • @David, how do you intercept the selection? With that out of the way the owner-draw solution would work. – Cosmin Prund Apr 07 '11 at 17:05
  • 1
    @Cosmin - Need to subclass the dropped down list box. There's an example in the comment I posted to the question. – Sertac Akyuz Apr 07 '11 at 17:24