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
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
Top, etc, shared by visual Windows forms controls. Note, also that the
Textbox provides the base class for
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
RadioButton controls aren't on that list. Where are they? See the
ButtonBase control in the list above? The
RadioButton controls derive from
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
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
However, there is a problem with the loop: it won't clear several controls correctly (perhaps most notably,
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
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
*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
CheckBox, and the
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
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
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
ProgressBars. Along the way we also covered casting and conditional expressions.