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!
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!
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.
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.