Customized radio groups

by Stephen Posey

If you have ever had to create a group of radio buttons using old-style dialog resource techniques, you know how convenient C++Builder and the VCL make this task with the TRadioGroup component. TRadioGroup conveniently encapsulates a group box containing a programmer-specified number of radio buttons.

While the VCL encapsulation removes much of the drudgery associated with creating radio button groups, it also conceals the fact that the individual radio buttons are themselves members of a collection of TRadioButton objects maintained by the TRadioGroup. This article will explain how to access the individual items in a TRadioGroup in order to create radio groups that can be customized to provide additional features.

Better radio groups

Sometimes it’s desirable to create a group of radio buttons that has more variety in its appearance or behavior than is provided by the standard TRadioGroup component. One can, of course, simply put individual TRadioButtons into a basic TGroupBox, but that requires more effort to keep track of radio buttons that are added, removed, or changed at runtime.

Is it possible to access the individual TRadioButtons inside a TRadioGroup? As you might have guessed, I wouldn’t be writing this article if it weren’t! As a TWinControl descendant, TCustomGroupBox (and, hence, TRadioGroup) inherits the ability to “parent” other components (in this context, parent refers to the Windows API notion of a “parent window,” and not the OOP notion of inheritance). This is manifested in the VCL by the Controls property that TWinControl and descendants possess.

Thus, the radio buttons maintained by TRadioGroup are accessible as TRadioButtons through the radio group’s Controls property. The list of TRadioButton components exposed by the Components property of TRadioGroup is populated in conjunction with the addition of elements to the Items property. Therefore, the standard TRadioGroup is the parent to only enough TRadioButtons to account for its contained radio buttons. The radio buttons contained in the Controls property are in the order they were added to the Items property, and as they appear visually.

Accessing the radio buttons

So, what can you do with this knowledge? Since you now have access to the individual radio buttons, and since they’re effectively identical to TRadioButton components (at runtime, at least), you can treat them in much the same way you do stand-alone TRadioButton components.

For example, TRadioGroup provides a single Hint property for its entire collection of radio buttons. With the code shown in Listing A, however, you can assign separate hints to each radio button individually. This code also assigns an OnClick event handler for each radio button (I will explain how to use the OnClick event in just a moment). Listing B provides a couple of small functions that simplify accessing the separate radio buttons. One function locates a radio button by its index, and the other locates a radio button by the text in the Caption property. Listing C shows how you could add emphasis to the selected radio button by changing its Font property.

Listing C also shows what originally prompted me to pursue this line of investigation. Recently on one of the Usenet programming groups, I ran across a question concerning TRadioGroup’s odd behavior regarding tab stops. If you set the standard TRadioGroup’s TabStop property to false and no item is selected, the radio group behaves as expected. That is, tabbing through the form does not stop on the radio group. However, if the ItemIndex property is set to something other than –1 (“no item selected”) or an item in the group is selected at runtime, that item receives focus in the tab order regardless of the state of the radio group’s overall TabStop property. By setting a radio button’s TabStop property to match that of the group when the radio button is selected, the radio group appears to exhibit a more intuitive behavior. I show how to do this in the radio group’s OnClick event handler, but it is also possible to do this (or anything else for that matter) by assigning event handlers to the events of the individual radio buttons. Listing 4 shows some of the possibilities. (Refer to Listing 1 for the code that assigns a method to the OnCreate event for each radio button.)

If you are adding items to the radio group at runtime, you’ll need to assign the event handler for the new items sometime after you create the radio button.

Other properties of TRadioButton that you might care to tinker with include Color, Enabled, HelpContext, PopupMenu, and Visible. One “gotcha” I ran across regarding changing properties of radio group items is that the items appear to be created with their widths set to the width of the group box. This means that the radio group might look odd if you change the Alignment property of a radio button. Similarly, if you change the background color, the change extends the entire width of the group box. You can change the Width property in code to alleviate some of this, but you have to be careful not to obscure the caption.

Conclusion

C++Builder and the VCL give us great flexibility and options for creating Windows programs easily. Of course, sometimes a little extra digging can make a good thing even better.

Listing A: The OnCreate event handler

void __fastcall TForm1::FormCreate(TObject *Sender)
{
  TRadioButton* rb;
  for (int i=0;i<RadioGroup1->ControlCount;i++) {
    rb = dynamic_cast<TRadioButton*>
      (RadioGroup1->Controls[i]);
    if (rb) {
      rb->ShowHint = true;
      rb->Hint = "Something about " + rb->Caption;
      rb->OnClick = RBOnClick;
    }
  }

Listing B: Radio button helper functions

TRadioButton* RadioButtonByCaption(
  TCustomRadioGroup* rg, String Caption)
{
  for (int i=0;i<rg->ComponentCount-1;i++) {
    TRadioButton* rb = dynamic_cast
      <TRadioButton*>(rg->Controls[i]);
    if (rb)
      if (rb->Caption == Caption) {
      return rb;
    }
  }
  return NULL;
}

TRadioButton* RadioButtonByIndex(
  TCustomRadioGroup* rg, int Index)
{
  return dynamic_cast
    <TRadioButton *>(rg->Controls[Index]));
}

Listing C: The radio group’s OnClick event handler modifies the radio buttons.

void __fastcall 
TForm1::RadioGroup1Click(TObject *Sender)
{
  // reset the color back to normal
  for (int i=0;i<RadioGroup1->Items->Count;i++) {
    RadioButtonByIndex(RadioGroup1, i)->Font =
     RadioGroup1->Font;
  }
  RadioButtonByIndex(RadioGroup1, 
    RadioGroup1->ItemIndex)->TabStop = 
    RadioGroup1->TabStop;
  RadioButtonByIndex(RadioGroup1, 
    RadioGroup1->ItemIndex)->Font->Color = clRed;
}

Listing D: The individual radio buttons’ OnClick event handler.

void __fastcall TForm1::RBOnClick(TObject *Sender)
{
  TRadioButton* rb = 
    dynamic_cast<TRadioButton*>(Sender);
  TRadioGroup* rg = 
    dynamic_cast<TRadioGroup*>(rb->Parent);
  for (int i=0;i<rb->Parent->ControlCount;i++) {
    TRadioButton* rb2 = dynamic_cast
      <TRadioButton*>(rg->Controls[i]);
    if (rb2)
      rb2->Font = rg->Font;
  }
  rb->Font->Color = clBlue;
  rb->Font->Style = rb->Font->Style << fsItalic;
  rb->TabStop = rb->Parent->TabStop;
}