Unlike AVR Classic's Clear opcode, AVR for .NET's Clear opcode cannot be used to clear a form, therefore you're left needing to clear all of the controls on an AVR for .NET Windows form manually. Part of the reason AVR for .NET's Clear opcode can't do this is because AVR for .NET has a much richer set of controls (including the ProgressBar, the TrackBar, and the PictureBox ) .

This article takes a look at the challenge of clearing a Windows form in AVR for .NET. If you're impatient and just want the answer to the challenge, zoom to the bottom of this article and copy the ClearControls() subroutine there. For the patient amongst you, this challenge is packed with ah-ha moments, especially for those of you coming to AVR for .NET from AVR Classic.

An inheritance refresher

AVR for .NET, unlike AVR Classic, is a fully object oriented language--almost everything is an instance of an object (most of the exceptions, data structures for example, are AVR compiler idioms). All .NET objects derive from System.Object or descendants of System.Object. For example, if you look at the Windows forms' Textbox (its full name is System.Windows.Forms.TextBox) documentation, you'll see its inheritance hierarchy:

Notice how the System.Windows.Forms.TextBox is five generations down from System.Object. Also, notice that three generations down from System.Object is the System.Windows.Forms.Control. System.Windows.Forms.Control is the base class for all Windows forms controls with a visual representation (the FileSystemWatcher, for example, has no visual representation). It provides all of the common methods and properties shared by Windows forms controls. This base class includes a surprising number of properties including Name, Text,Tag, Left, Top, etc, shared by visual Windows forms controls. Note, also that the Textbox provides the base class for DataGrid-specific textboxes.

If you look at the System.Windows.Forms.Control docs, as shown below,

You can also see what controls derive from it. Notice that most of the controls that could be on a form are listed here. It gets sneaky, though, because the CheckBox and RadioButton controls aren't on that list. Where are they? See the ButtonBase control in the list above? The Checkbox and RadioButton controls derive from ButtonBase.

This may seem like a bunch of gobbledygoop, but stick with me, it's important to understand, at least generally what a control's inheritance tree is. That knowledge opens lots of doors for you.

The Controls Collection

In .NET's Windows forms model, all forms controls that are containers all have a Controls property. The Windows form itself, as well as the Panel, the GroupBox, and the Tab control are all containers and have a Controls property. This property is a collection of the controls owned by the container.

With this knowledge, you easily write a little loop that traverses a form's controls like this:

ForEach ctl Type(System.Windows.Forms.Control) Collection(*This.Controls)
   // 
EndFor

Through the loop's ctl variable you can modify any properties exposed by System.Windows.Forms.Contols. For example, if you were only concerned with clearing TextBoxes you use this code:

Using System.Windows.Forms.Controls

ForEach ctl Type(System.Windows.Forms.Control) Collection(*This.Controls)
    ctl.Text = String.Empty
EndFor  

String.Empty is a static String class member that returns an empty string. If you prefer, you could also use:

clt.Text = ""

What you probably don't want to do is this:

ctl.Text = *Blank

That assigns one blank to the TextBox's Text property.

However, there is a problem with the loop: it won't clear several controls correctly (perhaps most notably, RadioButtonsCheckBoxes, or ComboBoxes. Because you've declared ctl of type System.Windows.Forms.Control type, you'll guarantee to always clear a Text property with the loop. However, some controls don't use the Text property for their value, but rather simply for associated text they display. We've still got work to do.

Casting from a lower-order object to a higher order object

In .NET (and most classically object oriented languages), any object can be assigned to System.Object.

DclFld s Type(*String) 
DclFld o Type(*Object) 

s = "Neil"      
o = s 

But, interestingly, you can't then do this:

s = o

The AVR compiler quickly tells you that it can't convert a type System.Object to type String. To make the assignment work, you must "cast" the o variable to a string like this:

s = o *As *String   

While it might seem like .NET compilers should be able to do this on their own, they can't. You can easily downshift any variable to a System.Object (or *Object in AVR parlance); but to put it back (to "upshift" it) you must cast it to its appropriate target type.

Consider this routine:

BegSr ClearControls
    ForEach ctl Type(System.Windows.Forms.Control) Collection(*This.Controls) 
        If (ctl *Is TextBox)    
            (ctl *As Textbox).Text = String.Empty
        ElseIf (ctl *Is CheckBox) 
            (ctl *As CheckBox).Checked = (ctl *As CheckBox).Checked = *False
        ElseIf (ctl *Is RadioButton) 
            (ctl *As RadioButton).Checked = (ctl *As RadioButton).Checked = *False
        EndIf 
    EndFor 
EndSr

The collection being iterated is the form's Controls property (where *This is a reference to the current form). This loop uses the *Is operator to check the runtime type of ctl to determine how to cast it to clear it.

(ctl *As CheckBox).Checked = False 

is an in-place cast and could have more verbosely been written like this:

DclFld cb Type(CheckBox) 

cb = ctl *As CheckBox           
cb.Checked = *False

For clarity, you might want to use this more verbose technique, but for our specific need here, the in-place cast works just fine.

AVR, like VB and C#, is type specific; once a variable is declared as a *Boolean you can't assign a *Date to it. The statically typed compiler won't allow it. It may seem like casting is doing just that, but it isn't. A *Date value isn't a higher order instance of a *Boolean value. It is an entirely different type of object. However, in the case of our loop, we know that all of the controls we're iterating all derive from the same base class. The loop can start out having "downshifted" all objects in the collection to a common base object; then at runtime we can interrogate the object to see if we can safely "upshift" from System.Object to its actual type (as we're doing with the Textbox, the CheckBox, and the RadioButton.)

Assigning Default Values

A radio button is typically used with other radio buttons to present a mutually exclusive selection (ie, Beatles, Van Morrison, or Warren Zevon) and are typically presented with one value checked, as the default value, unconditionally setting all of the radio buttons to false causes the default value to go missing.

We'll fix this by using the RadioButton's Tag property in which to stash the default value of the control.

radioButton1.Tag = *True
...
// Other default values assigned as needed.    
... 
BegSr ClearControls
    ForEach ctl Type(System.Windows.Forms.Control) Collection(container) 
        If (ctl.Controls.Count > 0)
            ClearControls(ctl.Controls)
        EndIf 
        If (ctl *Is TextBox)    
            (ctl *As Textbox).Text = Sting.Empty
        ElseIf (ctl *Is CheckBox) 
            If (ctl.Tag <> *Nothing) 
                (ctl.Tag *As CheckBox).Checked = ctl.Tag *As *Boolean
            Else 
                (ctrl.Tab *As CheckBox).Checked = *False 
            EndIf 
        ElseIf (ctl *Is RadioButton) 
            If (ctl.Tag <> *Nothing) 
                (ctl.Tag *As RadioButton).Checked = ctl.Tag *As *Boolean
            Else 
                (ctrl.Tab *As RadioButton).Checked = *False 
            EndIf
        EndIf 
    EndFor 
EndSr

The loop now uses a default value in the Tag property if it is present. And, although this example is just using the Tag property to assign a Boolean default, you could use the technique to give other controls other default values (for example, stash the default value of a selected item for a ComboBox). However, all that nesting makes the code a little hard to comprehend quickly.

Let's shorten the code with AVR's Conditional Expression syntax.

radioButton1.Tag = *True
... 
BegSr ClearControls
    ForEach ctl Type(System.Windows.Forms.Control) Collection(container) 
        If (ctl.Controls.Count > 0)
            ClearControls(ctl.Controls)
        EndIf 

        If (ctl *Is TextBox)    
            (ctl *As Textbox).Text = String.Empty
        ElseIf (ctl *Is CheckBox) 
            (ctl *As CheckBox).Checked = (ctl.Tag = *Nothing) ? +
                *False : ctl.Tag *As *Boolean
        ElseIf (ctl *Is RadioButton) 
            (ctl *As RadioButton).Checked = (ctl.Tag = *Nothing) ? +
                *False : ctl.Tag *As *Boolean
        EndIf 
    EndFor 
EndSr

Conditional Expression syntax details are shown below.

Coders inexperienced with conditional expressions may at first react negatively to them. At first glance, lines using conditional expressions looks very busy. But if you take just a few minutes to examine one the syntax is easy to understand. For my money using conditional expressions reduces comprehension friction caused by the nested if/else test in the previous version of the loop. If a conditional expression causes a line to be too long, you can extend the line (as shown in the figure above) with AVR's '+' line continuation (albeit this injects a little of its own comprehension friction!).

Clearing nested containers

One more thing to clean up and we're done. As written, our loop would clear only controls owned directly by the form; any controls owned by other control containers (eg, the Panel or GroupBox control) would not be cleared. That's easy to fix with recursion. The final version of the routine to clear controls in AVR for .NET looks like this:

radioButton1.Tag = *True

...

ClearControls(*This.Controls)

...

BegSr ClearControls
    DclSrParm container Type(System.Windows.Forms.Control.ControlCollection)

    ForEach ctl Type(System.Windows.Forms.Control) Collection(container) 
        If (ctl.Controls.Count > 0)
            ClearControls(ctl.Controls)
        EndIf 

        If (ctl *Is TextBox)    
            (ctl *As Textbox).Text = String.Empty
        ElseIf (ctl *Is CheckBox) 
            (ctl *As CheckBox).Checked = (ctl.Tag = *Nothing) ? +
                *False : ctl.Tag *As *Boolean
        ElseIf (ctl *Is RadioButton) 
            (ctl *As RadioButton).Checked = (ctl.Tag = *Nothing) ? +
                *False : ctl.Tag *As *Boolean
        EndIf 
    EndFor 
EndSr

With recursion in place, ClearControls() is first called passing *This.Controls as its argument. This clears controls owned directly by the form, but children controls that own controls (and their children and on and on) of any control are also cleared with the recursive call to ClearControls() using (ctl.Controls as its argument). Isolating ClearControls() from direct knowledge of the form it is clearing (that is, it is now free from references to *This) also lets you put this routine in a general purpose class library for efficient reuse.

There you have it! We took the long way around the barn to get there, but we now have a ClearControls() routine that can easily clear an entire form. And, it would be easy to extend for special cases such as ComboBoxes and ProgressBars. Along the way we also covered casting and conditional expressions.