SIMPOL Documentation

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.