6

I need to add a button (maybe TSpeedButton?) on every item of ComboBox. When one clicks the button the corresponding item is deleted from the list. For example:

enter image description here

I've seen similar discussion on SpeedButtons in string grids (here: TStringGrid with SpeedButtons), but I don't know how to implement all those things on ComboBox. Could you please give me some advice or links for further reading on the topic.

Community
  • 1
  • 1
Alex
  • 113
  • 6
  • Not necessary to be exactly TSpeedButton, but a button that has OnClick handler. Any suggestions? – Alex Jun 01 '15 at 19:37
  • I edited the question.. Have an image to illustrate, but the Rep is too low. – Alex Jun 01 '15 at 19:41
  • 1
    Anyway, I think you'll be looking to custom draw the combo items, including a delete button, and then detect the clicks. Personally I'd be trying to find a different UI. – David Heffernan Jun 01 '15 at 19:45
  • 1
    I mean I'd find a different way to let users delete items. Interacting with a combo drop down is pretty funky. – David Heffernan Jun 01 '15 at 19:51
  • Oh, I see. You've given me a clue - I think that it would be more convenient to delete a selected item in ComboBox (having drop down closed). – Alex Jun 01 '15 at 19:57
  • Should I just delete the question? – Alex Jun 01 '15 at 19:58
  • No. It's a good question. Leave it here. Nothing to gain by deleting. Some of the earlier comments could be deleted though. – David Heffernan Jun 01 '15 at 19:59
  • You can't do that with default ComboBox. The key is to pop up your own component which allows that. Have a look at TJvCheckedComboBox from JEDI JVCL. It shows own ListBox. You just want to display your own ListBox with buttons on the right side. – smooty86 Jun 01 '15 at 20:03
  • [subclass](http://stackoverflow.com/a/9165643) the list, owner draw buttons, capture mouse when left button down, draw pressed look, force list to repaint, delete item when mouse up if captured, draw non-pressed look, force list to repaint, set capture to the list back. Might have problems deleting an item when the list is dropped down. – Sertac Akyuz Jun 01 '15 at 20:04
  • And here is one example how to draw any control. When you join the code from TJvCheckedComboBox with this, you are done ... http://delphi.about.com/od/adptips2006/qt/radiolistbox.htm – smooty86 Jun 01 '15 at 20:07
  • 1
    Your users are going to hate this UI, IMO. It's not the same as deleting in a StringGrid where all of the rows are visible and you can see what happens when things are deleted. You're doing this in a list that will collapse and expand with things in a different order than where they appeared before. I'd seriously rethink this, and use a separate form to allow them to delete items (perhaps with a TCheckListBox, where they can check multiple items, and then a Delete button when they're ready to actually delete them). – Ken White Jun 01 '15 at 20:20
  • Indeed, if you delete an item, you need to somehow refresh the drop-down list. However, the list is drawn/prepared at the moment of opening up. So upon deletion, the drop-down will have to close up anyway. – Jerry Dodge Jun 01 '15 at 21:16
  • Thanks to everyone for commenting. I agree that it was not the best idea of the interaction with user. I will do the things another way. – Alex Jun 02 '15 at 03:41

1 Answers1

6

Besides the user experience comments aside, to which I agree, a solution to the question isn't really that hard.

You can do this by setting the Style property to csOwnerDrawFixed, drawing the items yourself in the OnDrawItem event, and deleting the selected item in the OnSelect event for example, as follows:

unit Unit1;

interface

uses
  Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
  Vcl.Imaging.PNGIMage;

type
  TForm1 = class(TForm)
    ComboBox1: TComboBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure ComboBox1Select(Sender: TObject);
  private
    FDeleteGraphic: TPNGImage;
    FDeleteRect: TRect;
  end;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  ComboBox1.Canvas.FillRect(Rect);
  if Index >= 0 then
    ComboBox1.Canvas.TextOut(Rect.Left + 2, Rect.Top, ComboBox1.Items[Index]);
  if (odSelected in State) and not (odComboBoxEdit in State) then
  begin
    FDeleteRect := Rect;
    FDeleteRect.Left := FDeleteRect.Right - FDeleteGraphic.Width;
    ComboBox1.Canvas.Draw(FDeleteRect.Left, FDeleteRect.Top, FDeleteGraphic);
  end;
end;

procedure TForm1.ComboBox1Select(Sender: TObject);
var
  MousePos: TPoint;
begin
  MousePos := ComboBox1.ScreenToClient(Mouse.CursorPos);
  MousePos.Offset(0, -ComboBox1.Height);
  if PtInRect(FDeleteRect, MousePos) then
  begin
    ComboBox1.Items.Delete(ComboBox1.ItemIndex);
    ComboBox1.Invalidate;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FDeleteGraphic := TPNGImage.Create;
  FDeleteGraphic.LoadFromFile('H:\Icons\FamFam Common\Delete.png');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FDeleteGraphic.Free;
end;

end.

With this result:

Screen shot

You might want to (re)store the previous ItemIndex setting. Customize to your wishes.

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • Surely `OnSelect` can be fired by keyboard actions. – David Heffernan Jun 01 '15 at 21:28
  • @David Sure. If the user choose to position the mouse cursor precisely within the delete icon, then maybe a hit on Enter might indicate a wanted deletion. Otherwise, set `FDeleteRect.Width := -1;` in the `OnKeyDown` event. – NGLN Jun 01 '15 at 21:36
  • Those are not like buttons but images. – Sertac Akyuz Jun 01 '15 at 21:38
  • @Sertac Yeah, can OP do something too? This can be fine tuned further in many ways. – NGLN Jun 01 '15 at 21:40
  • Buttons can be drawn in the same manner, and can even use the active style (if using VCL Styles). – Jerry Dodge Jun 01 '15 at 21:53
  • @Jerry buttons have down, hot look, you can't do that in onselect. – Sertac Akyuz Jun 01 '15 at 22:07
  • @Sertac Yes, that's another story. Still much better than an image as you pointed out. – Jerry Dodge Jun 01 '15 at 22:48
  • Or I should say, much closer to OP's request. – Jerry Dodge Jun 01 '15 at 22:54
  • @NGLN Yes, OnSelect is just what I need. Not the buttons, but the way to solve the problem. – Alex Jun 02 '15 at 03:52
  • +1 we need more answers like these. There's a full code example, a screenshot and a working solution, so why didn't this answer have any up votes? – Wouter van Nifterick Jun 02 '15 at 06:17
  • @Wouter It's a complicated subject matter. Usually answers that require greater expertise get fewer upvotes than answers on subjects that more people can understand and appreciate. Also, I suspect that the people that do understand the technical issues have reservations over the approach outlined. – David Heffernan Jun 02 '15 at 06:43