Ask Reuben – May 7, 2025
ui.RadioGroup
Why is there a ui.ComboBox class for COMBOBOX but not for other widgets?
How can I dynamically add items to a RADIOGROUP widget?
All experienced developers have at some point in their life, started off coding a project in one direction then decided a little later on that perhaps they need to go in another direction, or perhaps they have started off on one project, got distracted onto another project and never got back to finish or tidy up that original project.
For Genero, ui.ComboBox is one such area. It was implemented with plans to add other other similar classes for other widgets and containers, and then there was a thought that perhaps there should be a more generic ui.Field class, and for whatever reason, consider that this was over 20 years ago, 20 years later we still have ui.ComboBox as an orphan. There is no ui.Field, there is no ui.RadioGroup class yet. However what you should be aware of is that anything you are likely to want to code using ui.Field, or ui.RadioGroup etc is most likely implementable via AUI Tree manipulation and you can write your own code to do this.
For instance if you study ComboBox and RadioGroup fields in the AUI Tree you will see that they have the similar design where there is the FormField node, the Widget node (RadioGroup or ComboBox), and then one or more Items nodes as children containing what the user can select in the widget …


… and that you can write your own code to provide a Radiogroup equivalent of ui.ComboBox.
You will typically find that most Genero developers have written their own libraries to code what might not have an explicit ui dot something method. This code might similar to this code I have in Github in the fgl_auitree repository. For example, the RadioGroup equivalent of ui.ComboBox.addItem could be coded as …
FUNCTION ui_RadioGroup_addItem(rg ui_RadioGroup, name STRING, text STRING) DEFINE child om.DomNode -- Check that we are starting with a RadioGroup node IF rg.getTagName() != "RadioGroup" THEN -- Should throw an exception here RETURN END IF -- Add a new node of type Item LET child = rg.createChild("Item") -- Set the name and text attribute values CALL child.setAttribute("name", name) CALL child.setAttribute("text", text) END FUNCTION
This ability to code these functions to manipulate the AUI Tree is perhaps one of the reasons there has never been to date any push to tidy up the loose end and move beyond ui.ComboBox to ui.Radiogroup or ui.Field. We could remove ui.ComboBox but that would be unnecessarily disruptive so it has stayed as an orphan.
I could leave the article there but the ability to code Methods means that you can potentially be a little smarter about how you implement this code to manipulate the AUI tree. Below I have a ui_RadioGroup.4gl which can be considered the RadioGroup equivalent of ui.ComboBox. (I have only shown the forName, addItem, clear methods, if you wanted the equivalents of the other ui.ComboBox methods hopefully you can figure them out yourself)
#! ui_RadioGroup.4gl PUBLIC TYPE ui_RadioGroup_Type RECORD om om.DomNode END RECORD PUBLIC FUNCTION (rg ui_RadioGroup_Type) forName(field_name STRING) DEFINE field_node om.DomNode INITIALIZE rg.om TO NULL LET field_node = ui.Window.getCurrent().getForm().findNode("FormField", field_name) IF field_node IS NULL THEN -- try again prefixing with formonly. LET field_node = ui.Window.getCurrent().getForm().findNode("FormField", "formonly."||field_name) END IF -- Repeat replacing FormField with TableColumn and other alternates from ScrollGrid, Matrix IF field_node IS NOT NULL THEN LET rg.om = field_node.getFirstChild() IF rg.om.getTagName() = "RadioGroup" THEN # Good, it is a RadioGroup ELSE -- TODO, add exception INITIALIZE rg.om TO NULL END IF END IF END FUNCTION PUBLIC FUNCTION (rg ui_RadioGroup_Type) clear() DEFINE item_list om.NodeList DEFINE i INTEGER LET item_list = rg.om.selectByTagName("Item") FOR i = item_list.getLength() TO 1 STEP -1 CALL rg.om.removeChild(item_list.item(i)) END FOR END FUNCTION PUBLIC FUNCTION (rg ui_RadioGroup_Type) addItem(n STRING,t STRING) DEFINE item_node om.DomNode LET item_node = rg.om.createChild("Item") CALL item_node.setAttribute("name",n) CALL item_node.setAttribute("text",t) END FUNCTION
After using
IMPORT FGL ui_RadioGroup
to signify that I want to use this library, I am in position to have …
DEFINE rg ui_RadioGroup.ui_RadioGroup_Type similar to DEFINE rg ui.ComboBox
To set the items, I am in a position to have …
CALL rg.clear() CALL rg.addItem("Y","Yes") CALL rg.addItem("N","No") similar to CALL cb.clear() CALL cb.addItem("Y","Yes") CALL cb.addItem("N","No")
and its only the initialisation that is slightly different but not too different …
CALL rg.forName(n) versus LET cb = ui.ComboBox.forName(n)
Using such a library my Radiogroup is not limited to being what is hard-coded via the ITEMS attribute in the definition. I can programmatically add/remove items as required. I have in my toolbox, the ability to do with a RadioGroup what I can do with a ComboBox. The Function Methods functionality helps write tidy code here.
The code below is a little test program that imports the ui_RadioGroup.4gl library.
#! ui_RadioGroup_test.4gl IMPORT util IMPORT FGL ui_RadioGroup MAIN DEFINE rec RECORD cb1 CHAR(1), cb2 CHAR(1), cb3 CHAR(1), rg1 CHAR(1), rg3 CHAR(1) END RECORD DEFINE program_name STRING WHENEVER ANY ERROR STOP DEFER INTERRUPT DEFER QUIT OPTIONS FIELD ORDER FORM OPTIONS INPUT WRAP LET program_name = base.Application.getProgramName() CALL ui.Dialog.setDefaultUnbuffered(TRUE) CLOSE WINDOW SCREEN LET rec.cb1 = "Y" LET rec.cb2 = "Y" LET rec.cb3 = "Y" LET rec.rg1 = "Y" LET rec.rg3 = "Y" OPEN WINDOW w WITH FORM program_name ATTRIBUTES(TEXT=program_name) INPUT BY NAME rec.* ATTRIBUTES(WITHOUT DEFAULTS=TRUE, UNBUFFERED) BEFORE INPUT CALL populate_combo_by_fieldName("cb3") CALL populate_radio_by_fieldName("rg3") ON ACTION show MESSAGE util.JSON.stringify(rec) END INPUT END MAIN FUNCTION populate_combo_by_fieldName(n STRING) DEFINE cb ui.ComboBox LET cb = ui.ComboBox.forName(n) CALL populate_combo_yesno(cb) END FUNCTION FUNCTION populate_combo_yesno(cb ui.ComboBox) CALL cb.clear() CALL cb.addItem("Y","Yes") CALL cb.addItem("N","No") END FUNCTION FUNCTION populate_radio_by_fieldName(n STRING) DEFINE rg ui_RadioGroup.ui_RadioGroup_Type CALL rg.forName(n) CALL populate_radio_yesno(rg) END FUNCTION FUNCTION populate_radio_yesno(rg ui_RadioGroup.ui_RadioGroup_Type) CALL rg.clear() CALL rg.addItem("Y","Yes") CALL rg.addItem("N","No") END FUNCTION
#! ui_RadioGroup_test.per LAYOUT GRID { ComboBox (Items) [f01 ] ComboBox (Initializer) [f02 ] ComboBox (Function) [f03 ] RadioGroup (Items) [f04 ] RadioGroup (Function) [f06 ] } END END ATTRIBUTES COMBOBOX f01 = formonly.cb1, NOT NULL, ITEMS=(("Y","Yes"),("N","No")); COMBOBOX f02 = formonly.cb2, NOT NULL, INITIALIZER=populate_combo_yesno; COMBOBOX f03 = formonly.cb3, NOT NULL; RADIOGROUP f04 = formonly.rg1, NOT NULL, ITEMS=(("Y","Yes"),("N","No")), ORIENTATION=HORIZONTAL; -- No initializer for RADIOGROP, hence skipping f05, rg2 RADIOGROUP f06 = formonly.rg3, NOT NULL, ORIENTATION=HORIZONTAL; INSTRUCTIONS SCREEN RECORD scr(cb1, cb2, cb3, rg1, rg3);