Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: Is it possible to have a combo box with disabled items in it?

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!

like image 318
croceldon Avatar asked Apr 07 '11 15:04

croceldon


People also ask

How do I make my combobox disabled?

We can Enable or Disable the options in the given Combobox widget by providing the state property. The state property forces to make a widget either active or disabled. To disable the Combobox widget, we have to set the state property as readonly or disabled.


2 Answers

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.

like image 82
Andreas Rejbrand Avatar answered Oct 31 '22 17:10

Andreas Rejbrand


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.

like image 23
Ken White Avatar answered Oct 31 '22 18:10

Ken White