Forms and Form Controls
Now that we have windows, dialogs, and menus, it might be useful if we had something to actually put into the windows. That is where forms and form controls come in. This is the primary user-interface area that makes up the real "meat" of most applications.
Introduction to Forms
The form capability in SIMPOL is intended to provide the primary interface support for creating desktop applications. Forms are normally contained in windows or dialogs, so make sure that you read the section called “Windows and Dialogs” before trying to make use of the content of this section. Currently the following form control types are available for use on a form:
wxformtext — for providing labels and other text on the form
wxformedittext — provide controls for data-entry from the user
wxformbutton — pushbuttons for every occasion
wxformbitmapbutton — pushbuttons with images instead of text
wxformcheckbox — for getting individual choices from the user
wxformoption — to get one of a set of choices from the user
wxformcombo — to allow the user to select a single choice from a drop-down list or to enter a new option
wxformlist — to get one or more selections from a list of options
All of the form controls have the type tag wxformcontrol
so that a variable
can be created that can hold a reference to any valid form control.
Creating Simple Forms
The form has a background color, and a height and width. It also has a container property
that holds a reference to the current container object (or .nul
). Forms
and form controls exist outside of their visible representation. This is quite handy, since
a form does not need to be displayed or even be associated with a container and in spite of
that, the contents of the various controls can be assigned or read. A form can be moved from
one window to the next simply by calling the setcontainer()
method
of the form and passing the target window reference to the method.
To assign a color to a form, window, or form control, SIMPOL makes use of the rgb type. This provides a method of assigning a color using the standard RGB methodology. An rgb object has as its value the combined integer value of the color. This can also be used to assign colors to controls and forms, etc. but in general it is better to use an rgb object since under certain conditions the requested color may not be available and when the color is created using the rgb type the closest available color will be selected.
Creating a basic form is quite simple, as can be seen from the following program code:
function main() string sResult wxform f integer iErrnum wxwindow w // Initialize the error variable so that we are passing a valid // object to be filled. iErrnum = 0 // Create our initial form f =@ wxform.new(640, 400, error=iErrnum) if f =@= .nul sResult = "Error " + .tostr(iErrnum, 10) + \ " creating form{d}{a}" else // Assuming the form was created successfully, assign a color // to the background. Here, since we are creating the rgb // object from pure values, we should use the red, green, and // blue properties to ensure that a valid color is produced. f.setbackgroundrgb(red=0xa0, green=0xa0, blue=0xa0) // Create a window to contain the form w =@ wxwindow.new(0, 0, 640, 480, captiontext="Test form \ window", error=iErrnum) if w =@= .nul sResult = "Error " + .tostr(iErrnum, 10) + " creating \ window{d}{a}" else // Assuming the window was created successfully, assign the // function to handle someone clicking the close gadget. w.onvisibilitychange.function =@ quit // Now we assign the background color from the form to the // window so that they match. This time we can assign the // rgb object since it will be certain to be a valid color. w.setbackgroundrgb(f.backgroundrgb) // Now let's add a minimal title to the form, roughly // centered f.addcontrol(wxformtext, 285, 19, 70, 16, "Test Form") // Finally, we move the form into the window that we have // prepared f.setcontainer(w) // Enter the wxprocess() function and wait for events // The user pressing the close gadget (or Alt+F4) will cause // the onvisibilitychange method to fire which will then // call the wxbreak() function. That will cause it to drop // out of the wxprocess() function, ending the program. wxprocess(.inf) sResult = "Success{d}{a}" end if end if end function sResult function quit(wxwindow w) wxbreak() end function
The majority of what is going on can be read from the program comments in the code. Note that the
form creation actually takes place before the window creation. This is a very basic form program
that doesn't have all that much happening. Still, it is an excellent little program to study, since
it teaches a number of very important concepts when creating forms. It is also a good idea to read
the section in the "SIMPOL Language Reference" regarding the form and each of the form controls.
The form's addcontrol()
method has been designed to allow virtually
all of the various properties for each control to be set when the control is created. This will
allow the program code to be compacter for those who prefer such an approach. Many people complained
about the wordiness of the object SBL code.
Although it is a little early in the life-cycle of the SIMPOL language to talk about
best practice there are certain things that are standard with most event-driven
programs that can already be applied here. Most programs can be broken down into three pieces:
initialization, execution, and termination. Initialization sets up the program, execution runs the
program, and termination closes the program performing any necessary cleanup. From this we can see
that the previous program is initialized, then enters the wxprocess()
function
and remains there processing events until some event results in the wxbreak()
function being called. The termination of the program is quite minimal since SIMPOL tends to cleanup
most things for you, all that is left is assigning the return value of the program and returning it.
Working with Form Controls
Now that we have had a chance to work a bit with forms, the next step is to add more controls to the
form. As such, the next program wxforms2.smp
builds on the work done in the first
program. It will look a bit more complicated, but not too much more.
function main() string sResult wxform f integer iErrnum wxwindow w type(wxformcontrol) fc wxfont font // Initialize the error variable so that we are passing a valid // object to be filled. iErrnum = 0 // Create our initial form f =@ wxform.new(640, 400, error=iErrnum) if f =@= .nul sResult = "Error " + .tostr(iErrnum, 10) + " creating form{d}{a}" else // Assuming the form was created successfully, assign a color to // the background. Here, since we are creating the rgb object // from pure values, we should use the red, green, and blue // properties to ensure that a valid color is produced. f.setbackgroundrgb(red=0xc0, green=0xc0, blue=0xc0) // Create a window to contain the form w =@ wxwindow.new(0, 0, 640, 480, captiontext="Test form \ window", error=iErrnum) if w =@= .nul sResult = "Error " + .tostr(iErrnum, 10) + " creating \ window{d}{a}" else sResult = "Success{d}{a}" // Assuming the window was created successfully, assign the // function to handle someone clicking the close gadget. w.onvisibilitychange.function =@ quit // Now we assign the background color from the form to the // window so that they match. This time we can assign the rgb // object since it will be certain to be a valid color. w.setbackgroundrgb(f.backgroundrgb) // Now let's add a minimal title to the form, roughly centered fc =@ f.addcontrol(wxformtext, 285, 19, 70, 16, "Test Form") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) font =@ wxfont.new("Arial", 10) fc.setfont(font) // Now a few more controls just for playing with // a place to type: fc =@ f.addcontrol(wxformtext, 39, 84, 98, 16, "Editable text\ box") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc =@ f.addcontrol(wxformedittext, 146, 82, 175, 20) fc.setbackgroundrgb(red=0xFF, green=0xFF, blue=0xFF) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) // some option buttons fc =@ f.addcontrol(wxformoption, 146, 132, 63, 22, "one") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc =@ f.addcontrol(wxformoption, 146, 154, 63, 22, "two") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc =@ f.addcontrol(wxformoption, 146, 176, 63, 22, "three") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) // a check box fc =@ f.addcontrol(wxformcheckbox, 148, 107, 70, 22, "Do it") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) // a combo box fc =@ f.addcontrol(wxformcombo, 148, 210, 172, 126, edittype=\ "droplist") fc.setbackgroundrgb(red=0xFF, green=0xFF, blue=0xFF) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc.insert("red") fc.insert("green") fc.insert("blue") fc.insert("yellow") fc.insert("magenta") fc.insert("cyan") // a list box fc =@ f.addcontrol(wxformlist, 381, 103, 182, 217, \ selectiontype="extended") fc.insert("red") fc.insert("green") fc.insert("blue") fc.insert("yellow") fc.insert("magenta") fc.insert("cyan") fc.setbackgroundrgb(red=0xFF, green=0xFF, blue=0xFF) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc =@ f.addcontrol(wxformtext, 381, 85, 117, 16, "Choose one\ or more") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) fc =@ f.addcontrol(wxformtext, 69, 212, 69, 16, "Choose one") fc.setbackgroundrgb(red=0xC0, green=0xC0, blue=0xC0) fc.settextrgb(red=0x0, green=0x0, blue=0x0) fc.setfont(font) // and some buttons: an OK and a Cancel button fc =@ f.addcontrol(wxformbutton, 226, 352, 83, 27, "OK") fc.setfont(font) // here we assign the event handling function fc.onclick.function =@ evaluateform // and here we assign a reference to the object we want passed // to the function. fc.onclick.reference =@ sResult fc =@ f.addcontrol(wxformbutton, 334, 352, 83, 27, "Cancel") fc.setfont(font) fc.onclick.function =@ evaluateform fc.onclick.reference =@ sResult // Finally, we move the form into the window that we have // prepared. f.setcontainer(w) // Enter the process() function and wait for events // The user pressing the close gadget (or Alt+F4) will cause // onvisibilitychange method to fire which will then call the // the wxbreak() function. That will cause it to drop out of // the wxprocess() function, ending the program. // Also, clicking on either of the wxformbutton controls will // call the evaluateform() function which in turn will read the // contents controls and assign that plus which button was // pressed to the string of the object referred to by sResult. // Then it will call the wxbreak() function with the same result // as above. wxprocess(.inf) end if end if end function sResult function quit(wxwindow w) wxbreak() end function function evaluateform(wxformbutton b, string s) type(wxformcontrol) fc integer i boolean bDoneone if b.text == "Cancel" s = "You pressed Cancel{d}{a}" else s = "You pressed OK{d}{a}" end if fc =@ b.form.firstcontrol while if fc.type =@= wxformedittext s = s + "You typed: " + fc.text + "{d}{a}" else if fc.type =@= wxformcheckbox s = s + "Do it was " + .if(fc.state == "", "not ", "") + \ "checked{d}{a}" else if fc.type =@= wxformoption if fc.state == "on" s = s + "You selected the option '" + fc.text + "'{d}{a}" end if else if fc.type =@= wxformlist i = fc.getselected(1) bDoneone = .false if i == .nul s = s + "You did not select an entry from the list{d}{a}" else while if not bDoneone s = s + "You selected the following items from the \ list:{d}{a}" bDoneone = .true end if s = s + " '" + fc[i] + "'{d}{a}" i = fc.getselected(i + 1) end while i == .nul end if else if fc.type =@= wxformcombo if fc.text >= "" s = s + "You selected the entry '" + (fc.text) + "' from \ the combo box{d}{a}" else s = s + "You did not select an entry from the combo \ box{d}{a}" end if end if fc =@ fc.next end while fc =@= b.form.firstcontrol wxbreak() end function
As we can see from the previous program listing, although it is somewhat longer than the first
example, it is not greatly different. It simply contains more controls and an additional event
handling function. The technique for exiting the wxprocess()
function is
identical to the one used by the quit()
. In both cases the
wxbreak()
function is called.
Creating the form control information by hand could be quite long-winded, but fortunately the
content shown in the program was not created by hand, but rather generated using a utility
program from a Superbase form created with the Superbase Form Designer. Until a form designing
utility has been created for SIMPOL-based forms, we will use the older Superbase Form Designer
to provide this functionality. The current utility can convert all of the supported form objects
including the event procedure names. The actual utility is more sophisticated than the example
code shown here. It also creates an array of controls that is indexed according to the original
control name from the Superbase form and which provides the form control reference as the array
element. This makes it fairly easy to work with the form controls since they are all placed into
the form controls array. Later in the life-cycle of the forms, this will probably not be needed
but the technique will still work and be valid. The form conversion utility is in the
utilities
directory and the program is called:
sbv2wxsm.sbp
.
The Grid Control
In this section we will look at the grid control. In SIMPOL one of the new controls that has been added to the mix is a general purpose grid control. The grid control can be used for any number of things. Internally in SIMPOL we will use it for implementing record and table view, for creating and modifying database table definitions, as the properties grid for the form and report designers, and much more. The grid functionality is currently a moving target, so this section will be regularly revisited during the pre-release cycle as new capabilities are added to the grid control. The most current information will always be found in the SIMPOL Language Reference book.
Let's build a little sample program, like the other programs that went before, to play a bit with
the grid control. As is the case with the other sample programs, this program will always be found
in the projects\examples
directory. This program will be
called wxgrid.sma
.
function main() wxform f wxwindow w wxformgrid g integer e string s s = "" e = 0 w =@ wxwindow.new(0, 0, 800, 600, visible=.false, \ captiontext="wxgrid example", \ border="simple", maxbutton=.false, error=e) if w =@= .nul s = "Error " + .tostr(e, 10) + " creating window{d}{a}" else // Assign the function to handle the user clicking the close gadget w.onvisibilitychange.function =@ quit // Create a new form f =@ wxform.new(w.innerwidth, w.innerheight, 0xC0C0C0, error=e) if f =@= .nul s = "Error " + .tostr(e, 10) + " creating form{d}{a}" else // Add the grid control to the form g =@ f.addcontrol(wxformgrid, 1, 1, f.width - 2, f.height - 2,\ rowcount=50, colcount=30, error=e) if g =@= .nul s = "Error " + .tostr(e, 10) + " creating grid{d}{a}" else // Change some of the labels, just to show we can g.setcollabels(startcol=1, "a column label", "b", "3", "d", \ "5", "Foo", "gosh!") g.setrowlabels(startrow=1, "a very long row label", "bb", \ "C", "IV") // Increase the width of the row labels to accomodate the // long one g.setrowlabelwidth(130) // Now we create a cell with a set of choices (combo box), // we could easily assign this to multiple cells in the // same statement g.setcellchoices(row=2, col=2, allowothers=.false, \ "<pick one>", "United Kingdom", \ "United States", "Germany", "France", \ "Italy", "Sweden", "Spain", "Portugal", \ "Norway", "Denmark", "Belgium", \ "Netherlands", "Luxembourg", "Greece", \ "Ireland", "Austria", value="<pick one>") // Assign some normal text content to one cell g.setcellvalue(3, 3, "This is read only") // Now make that cell read only g.setcellreadonly(3, 3, .true) // Widen the first column just to show we can g.setcolwidths(col=1, 190) // Assign the same text to a bunch of cells in a range g.setcellvalue(startrow=5, endrow=10, startcol=4, endcol=5, \ value="This is sooooo cool!!") // Widen the two columns to which we assigned the text g.setcolwidths(startcol=4, endcol=5, colwidth=130) // Assign a multi-line text. Currently it is not possible // to edit multiline text and allow the entry of new line // characters. g.setcellvalue(row=7, col=1, \ value="This is a text that goes over{a}\ multiple lines. We will also increase{a}\ the height of the row to compensate") // Now we can increase the height of the row to show the // multiline text g.setrowheights(row=7, rowheight=60) // Place the form into the window f.setcontainer(w) // Now show the window and wait for events w.setstate(visible=.true) wxprocess(.inf) end if end if end if end function s function quit(wxwindow w) wxbreak() end function
There are numerous other things that we can do with the grid control, this is only a very simple example. More capabilities will be added gradually as they become necessary, but already using the current state of the grid control, a great deal can be done, just use your imagination!
Summary
In this section we have looked at the basics of working with forms in SIMPOL. For more information about the specifics of working with each individual control, see the appropriate sections in the "SIMPOL Language Reference". Working with the forms is not greatly different than working with forms in the older Object SBL language. The event type that is embedded in the various controls is similar to the events from the older language. One notable difference is that an optional reference property, which must be an object reference, can be assigned. This provides a solution to the question of how to pass important information into the event handling function. If more than one piece of information is required then a user-defined type can be created that consolidates all of the information that is needed into a single object that is then passed around as required. A major advantage of this approach is that if later more information is needed, the interface of the functions need not be changed, merely the definition of the type needs to be expanded to include the additional information.