3

I'm using Delphi7 and I'd like to have a ComboBox with separator items (Just like in popup menus).

I've seen this beautifully implemented in Mozilla Sunbird (I know, it's not Delphi...) the following way:

  1. The separator item is a simple gray line drawn in the center of the item

  2. If you hover over the separator with the mouse, the selection doesn't appear

  3. If the user clicks the separator, it's not selected either AND the combobox doesn't closeup.

No. 1 could be implemented using DrawItem. I could live without No. 2 because I have no idea about that.

For No. 3 I'm asking for your help. I've figured out that straight after closing up a CBN_CLOSEUP message is sent to the combobox.

I thought about hooking the window proc and if CBN_CLOSEUP is sent to a certain combobox then countering it. But I'm unsure if this is the best solution, or maybe there are other, more elegant ways?

Whatever the solution is, I'd like to have a standard ComboBox which supports WinXP/Vista/7 theming properly.

Thanks!


Edit: For a working component please see this thread:

Can you help translating this very small C++ component to Delphi?

Community
  • 1
  • 1
Steve
  • 2,510
  • 4
  • 34
  • 53
  • 1
    You've asked for a standard combo box, but you've also asked for a combo box that has separators. Those are mutually exclusive requests. Mozilla's combo box can do it because it's *not* an OS-provided control; Mozilla provides all its own code for all its controls so it can work consistently on all supported platforms. – Rob Kennedy Dec 04 '10 at 22:02
  • Mozilla's widgets are a constant source of irritation to me. They achieve consistency in exactly the wrong way, consistent across platforms but users tend to run on a single platform. The consistency you really want is with the native platform and they can't do it. Is there anything other than Qt that can achieve cross-platform yet native widgets? – David Heffernan Dec 04 '10 at 22:37
  • 1
    Mozilla's enhanced comboxbox fits wonderfully into the Win environment and adds a brilliant idea with combobox separators. But I agree with you that iTunes/Safari for Windows is very bad and confusing (althought it's goal is obviously to make transitioning from a Win PC to a Mac easier)... – Steve Dec 04 '10 at 23:19
  • I just hate that they don't do things with native platform UX elements. Have you seen Firefox 4's new menu idiom? It's astonishingly bad. – David Heffernan Dec 05 '10 at 09:59

3 Answers3

2

I played around with making unclickable separator items (as described in this answer) and ran into several UI glitches. The problem is that combo boxes have several aspects to their behavior that can be hard to get exactly right:

  • Pressing the up and down arrow keys navigates the list while the list is dropped down.
  • Pressing Enter closes the dropped down list, selecting the current item.
  • Pressing Escape closes the dropped down list, selecting the current item (if the current item was chosen with the up and down arrow keys) or the last selected item.
  • If the combo box has the focus, then pressing the up and down arrow keys to changes the current selection without displaying the list.
  • If the combo box has the focus, then typing anything selects the combo box item matching whatever is typing.
  • If the combo box has the focus, then pressing F4 drops down the combo box list, which can then be controlled by keyboard or mouse.

Ensuring that disabled separator items don't respond to any of these events (plus any other events which I may be missing, e.g., screen readers?) seems fraught with error.

Instead, the approach I'm using is to draw the separator as part of the item:

  1. Use a variable height owner draw combo box.
  2. Add 3 pixels to the height for any items that need separators.
  3. Draw a horizontal line at the top of each item needing a separator.

Here's some C++Builder code to accomplish this; translating it to Delphi should be easy enough.

void __fastcall TForm1::ComboBox1DrawItem(TWinControl *Control,
    int Index, TRect &Rect, TOwnerDrawState State)
{
  bool draw_separator = NeedsSeparator(Index) && 
      !State.Contains(odComboBoxEdit);

  TCanvas *canvas = dynamic_cast<TCustomCombo*>(Control)->Canvas;
  canvas->FillRect(Rect);

  TRect text_rect = Rect;
  // Add space for separator if needed.
  if (draw_separator) {
    text_rect.Top += 3;
  }

  canvas->TextOut(text_rect.Left + 3,
      (text_rect.Top + text_rect.Bottom) / 2 - 
        canvas->TextHeight(ComboBox1->Items->Strings[Index]) / 2), 
      ComboBox1->Items->Strings[Index]);

  // Draw a separator line above the item if needed.
  if (draw_separator) {
    canvas->Pen->Color = canvas->Font->Color;
    canvas->MoveTo(Rect.Left, Rect.Top + 1);
    canvas->LineTo(Rect.Right, Rect.Top + 1);
  }
}

void __fastcall TForm1::ComboBox1MeasureItem(
    TWinControl * /* Control */, int Index, int &Height)
{
  Height = ComboBox1->ItemHeight;

  // Add space for the separator if needed.
  if (Index != -1 && NeedsSeparator(Index)) {
    Height += 3;
  }
}
Community
  • 1
  • 1
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • Sadly, using current (Alexandria) version, the Combobox dropdown doesn't size well when one or more items have a higher Height than the rest. Suddenly there is a scrollbar and items even stay hidden until scrolled to (depending). Other than that, cool implementation ! – Peter Nov 01 '22 at 06:31
1

What you want is an owner-drawn combobox. See this: http://delphi.about.com/od/vclusing/a/drawincombobox.htm

Also, this seems to solve making the item unclicable: http://borland.newsgroups.archived.at/public.delphi.vcl.components.using.win32/200708/0708225320.html

As far as I know there is no VCL way of doing that, so you'll have to subclass the combobox. It would be nice to create component encapsulating those functionalities so you can reuse them easily.

God bless

Trinidad
  • 2,756
  • 2
  • 25
  • 43
  • Yes I've also found that page via Google, but how do I prevent the combobox from closing up, when the user clicks the separator item? – Steve Dec 04 '10 at 21:03
  • Thanks, I think I've found the component you linked in your edit the same time :-) – Steve Dec 04 '10 at 23:42
0

If you want your controls to look good use the free SpTBXLib. It supports combo style components which popup a popup menu with lines.

Jan Derk
  • 2,552
  • 27
  • 22
  • 2
    I couldn't disagree more; it's most important for your components to use native platform theming, unless of course you want to ape iTunes/Safari for Windows! – David Heffernan Dec 04 '10 at 18:04
  • Jan you didn't read my post well. I said I'm sticking to the standard XP/Vista/7 themes and avoiding any skinned/100% ownerdrawn components at all costs! – Steve Dec 04 '10 at 21:04
  • @Steve you'll need owner drawn surely though so my guess is that you need to get into uxtheme coding which is a ruddy nightmare! – David Heffernan Dec 04 '10 at 22:38
  • @David: UXTheme coding is very simple. I do it all the time: http://stackoverflow.com/questions/3986067/delphi-windows-7-control-panel-component/3991408#3991408, http://stackoverflow.com/questions/3900833/custom-control-creation-in-delphi/3902049#3902049, etc. – Andreas Rejbrand Dec 04 '10 at 22:55
  • I think there should be an easier solution by overriding the WndProc of the component, please see my edit and the reference to another thread above... – Steve Dec 04 '10 at 23:18
  • 1
    @Steve and @David: SPTbxLib supports standard Windows themes very well. Yes you can do skinning, but you don't have to. I can understand that you don't want to use third party components, but if you want the full functionality that you are asking for you are basically writing your own component. I am merely pointing out that others already have done the job for you. – Jan Derk Dec 05 '10 at 00:28
  • @Jan thanks for correcting my lack of knowledge of this component - I've nothing against good quality 3rd party components but hate non-native components - sounds like SPTbxLib supports native painting so my mistake. – David Heffernan Dec 05 '10 at 08:58