RadioButton Mystery Solved
Copyright © 1999-2006 Clayton Jones

by Clayton Jones
The Xbase Part XbpRadioButton has a problem with how the xbeP_Selected event is triggered.   According to the xbase++ documentation:

"The xbeP_Selected event is generated when the radiobutton changes its state from "not selected" to "selected". This occurs when the user clicks the radiobutton with the left mouse button, or using the space bar if the XbpRadioButton object has input focus. The value is changed in the edit buffer when this event occurs."

     This description is correct.  The problem is that the event is also triggered whenever an unselected radio button gets focus as a result of keyboard navigation.  Specifically, whenever you navigate to a radio button via keyboard (either by TABbing to a new group and landing on the first one, or by using the arrow keys to navigate within a group), if the radio button you navigated to is not already selected, the xbeP_Selected event will fire when it gets focus, and the codeblock in the :selected callback slot will be evaluated.  However, the logical value in the edit buffer is not changed, which means that the black dot does not change. This leaves the user unaware that the code in the codeblock is being executed.  Depending on how this codeblock is used, this could have very undesirable results.

     This behavior can be easily demonstrated with the following code, slightly modied, from the XbpRadioButton documentation. Copy this into a dialog window and compile. Be sure that the window has a pushbutton of some sort so that the TAB key may be used to navigate between the push button and the group box:

******* XbpStatic to act as parent and owner of the
******* radiobuttons
oStatic := XbpStatic():new(oDlg:drawingarea,,{175,20},{120,120} )

oStatic:caption := "COM Ports"

******* Define code block for :selected callback slot
bSelected := {|mp1,mp2,obj| tone(4) }

******* Create three radiobuttons
oRadio := XbpRadioButton():new(oStatic,,{20,75},{80,20} )
oRadio:caption := "COM 1"
oRadio:selected := bSelected
oRadio:tabStop := .T. // 1st one gets focus after TAB key
oRadio:group := XBP_BEGIN_GROUP

oRadio := XbpRadioButton():new(oStatic,,{20,45},{80,20})
oRadio:caption := "COM 2"
oRadio:selection := .T. //2nd one is already selected
oRadio:selected := bSelected
oRadio:group := XBP_WITHIN_GROUP

oRadio := XbpRadioButton():new(oStatic,,{20,15},{80,20})
oRadio:caption := "COM 3"
oRadio:selected := bSelected
oRadio:group := XBP_END_GROUP

     This creates a group box with three radio buttons, with the second one already selected. The :TabStop := .T. in the first button ensures that it will receive focus when the TAB key is pressed. The :group constants ensure that the arrow keys will navigate among the buttons within the group.

     Notice the call to tone() in the code block, which is assigned to the :selected ivar for each button. This tone will sound every time the event is triggered.

     After this is compiled, notice that when you press TAB to enter the group, the tone sounds when the first button get focus.  When you navigate with the arrow keys the tone also sounds.  Notice, however, the tone does not sound when the selected button receives focus.  Now use the mouse and space bar to legitimately select a button.  The tone again sounds.  Clearly, the event is triggered by keyboard navigation actions as well as legitimate selection actions.

Solving the Mystery

     The clue to figuring out and taming this bug came from the user defined class, XbpRadioGroup, found in the \Source\Samples\Solution\RadioGrp sub-directory.  I noticed that this deviant behavior was not evident in the example, so I set out to discover why.

     In the class’s create() method the :selected codeblock contains a call to a class method named selected(), with the event-generated values sent in as arguments:

     bSelected := {|mp1,mp2,obj| ::selected(mp1,mp2,obj)}

     Within this method, mp1 (which contains the T/F value of the edit buffer) is evaluated, with different actions taken according to the result.    It turns out that whenever the event is triggered by a legitimate selection action, mp1 is True, and it is False when triggered by a keyboard navigation action.  This also demonstrates why the black button does not change during keyboard navigation - because the actual edit buffer value is not changed.  Only the event is triggered.

     A careful reading of the first sentence in the above documentation excerpt indicates that the event is generated as a result of the edit buffer value changing from .F. to .T.  This explains why the black dot doesn’t change during arrow key navigation: because the value in the edit buffer has not changed. Clearly, the event is also being triggered when a radio button receives focus while not selected.

Note: If we try our tone() test in the :selected() method, we will observe the same behavior as in our earlier test.  It is clear that whoever authored this class was aware of, and accounted for, this behavior.

The Workaround

     Where does this leave us?  What if the XbpRadioGroup class does not suit a particular need? Must we always have a separate event handling routine to manage our radio buttons when we create our own groups?  Fortunately, no.   There is an easy workaround.  When defining a codeblock for :selected, be sure to test the value of mp1 using the iif() function. If the event has been triggered by a legitimate selection action, mp1 will be .T. and the code will execute:

     bSelect := {|mp1,mp2,obj| iif(mp1,<your code here>,nil)}

If it is necessary to have several expressions evaluated, use eval() to embed a nested codeblock:

     bSelect := {|mp1,mp2,obj| iif(mp1,eval({||<code,code,code>}),nil)}

Here is an example using :getData() when the :dataLink ivar has a codeblock:

     bSelect := {|mp1,mp2,obj| iif(mp1,obj:getData(),nil)}

     It is important to note that the behavior of the XbpRadioButton (after our fix) follows CUA standards for Radio Buttons.   Microsoft’s Word97 and Excel97 (probably others, too) violate this standard.   In those applications, when navigating among a group using the arrow keys, each button is selected automatically as it receives focus.  Pressing the space bar is unneccessary.

     In my opinion, this restricts the number of ways in which radio buttons may be used, and requires greater care in choosing what code goes into the :selection codeblock.  Whatever is there will be executed every time one of the buttons receives focus during keyboard navigation.  Interestingly, I created a simple group of buttons using Delphi-3 and observed the same behavior as Microsoft’s.   My own preference is for the standard CUA behavior.

     With this simple workaround, we have an acceptable solution until it is fixed by the development team.  The question is, which way would you like them to fix it: CUA or Microsoft?

Copyright © 1999-2006 Clayton Jones
All rights reserved.

Return to: Articles Page |Top-Down Page | Software And Services | Home