Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Create Custom Class Definitions that Resize Themselves!
John Adams, January 1, 2001
In Windows programming, users should have nearly complete control over how the program looks. Properties such as background color, fonts, and the like should be left to the users discretion. The same can be said of VFP applications. Numerous articles have been written about VFP becoming part of the ...
In Windows programming, users should have nearly complete control over how the program looks. Properties such as background color, fonts, and the like should be left to the users discretion. The same can be said of VFP applications. Numerous articles have been written about VFP becoming part of the Microsoft family of development environments. As such, we should try to incorporate as many Windows design philosophies into our VFP applications as possible. In addition to adherence to Windows design principals, implementation of these things gives your applications a polished professional look.

The focus of this article is a methodology for allowing users to resize forms within your applications. This article offers one approach to implementing resizing, but is by no means the only solution.

Two Ways Windows Applications Handle Resizing

Generally, there are two ways in which Windows programs handle resizing. The first method adds scrollbars to the current window when all the items to be displayed can’t be viewed in the current window. The program generally determines whether or not to show the scrollbars by calculating the size of the display in comparison to the size of the current window. The second method involves resizing the items to be displayed, and forcing it to fit into the current size of the window. VFP does not inherently support scrollbars on forms, as such, the first option would involve some substantial programming to implement. VFP does give us a resize method on most controls, so the second option seems to offer easier implementation.

Everything is Relative

The size of virtually all visual objects are relative to the screen, the object’s parent, and to each other. The key to performing effective resizing is in maintaining these relative relationships. Failure to maintain these relationships can create some display problems that range from unattractive, to completely disastrous in the functioning of you applications. (Imagine if a user can no longer see "Save" and "Cancel" buttons on an editing form!)

There are 4 or 5 key items that must be noted to determine the relative size of the object: Top, Left, Width, Height, and Fontsize (for those objects that have fonts). To ensure that these size and position indicators retain their relativity, we must create custom properties to hold relative size indicators. These relative size indicators will hold a percentage value that is derived from dividing the current size/position, by the object’s parent’s size.

To make certain that these percentages are properly calculated, we should calculate the values before the user has the opportunity to resize the form, in other words, in the "init" method.

To "Resize" or Not to "Resize"...

Problems with the Resize method

The resize method, while offering a convenient place to stick code has some problems. Some of the problems are:

  • Not all objects have a resize method

  • The resize method of a container does not inherently call
    the resize methods of all children

  • Default Resize methods do not necessarily behave the same
    way for every control.
To work around these problems, we will create our own resize method, and place it in each class definition.

A New and Improved Resize Method

By creating our own resize method, we can eliminate nearly all of the problems with the resize method listed above. For the purpose of this article, we will call the new resize method "JT_RESIZE". JT_RESIZE will hold all of the code needed to calculate our relative size indicators, as well as the code to recalculate the objects size and position.

The Basics

Resizing Methodology

The primary assumption that makes the resizing methodology work is that everything is relative to its parent, regardless of whether its parent is a form, a page, or a grid. By determining the relative size and position of each object in relation to its parent, we can maintain that relationship regardless of the size of the form.

Relative Position Holders

Each object must have custom properties added to hold the values of the relative position / size indicators. Therefore, we need to add the following properties to each object’s class definition.

Relative Height (REL_HEIGHT)
Relative Width (REL_WIDTH)
Relative Top (REL_TOP)
Relative Left (REL_LEFT)
Relative Font Size (REL_FONTSIZE) (for those objects that display text)

Init Event

Once we have our custom properties defined, we must capture the objects relative position to its parent and store them in the properties we have created. We do this as follows.

this.rel_height = this.height/this.parent.height
this.rel_width = this.width/this.parent.width
this.rel_top = this.top/this.parent.height
this.rel_left = this.left/this.parent.width
this.rel_fontsize = this.fontsize/this.height

One problem with this method is that pages on pageframes, and columns in grids, do not have height and width properties. Both rely on the height and width of their parent (pageframe, and grid respectively). This is easily handled, though, by testing for the parent object’s baseclass. As we know, the baseclass of an object is always the base VFP class. Using the baseclass property of the parent avoids problems with sub-classes which do not have the same class name as the baseclass. For instance, all pages, whether named page1, or
firsttab, have a baseclass of Page. The revised code to test for parent’s baseclass looks like this:

if upper(this.parent.baseclass) <> "PAGE".and.;
 upper(this.parent.baseclass) <> "Column"
   this.rel_height = this.height/this.parent.height
   this.rel_width  = this.width/this.parent.width
   this.rel_top = this.top/this.parent.height
   this.rel_left = this.left/this.parent.width
   this.rel_fontsize = this.fontsize/this.height
   else
   this.rel_height = this.height/this.parent.parent.height
   this.rel_width = this.width/this.parent.parent.width
   this.rel_top = this.top/this.parent.parent.height
   this.rel_left = this.left/this.parent.parent.width
   this.rel_fontsize = this.fontsize/this.height
endif
* Note: Fonts displayed can actually be relative to the parent’s height, or the height of the object itself.

Resize Event (Forms Only)

Resize, in VFP terms, is an event. We can create custom methods for our classes, but we cannot create events. Therefore, we must use the resize event on the form, which is triggered by user action, to determine when to execute the our custom resize method (JT_RESIZE). The code in the form’s resize event appears as follows.

Local i
For i = 1 to this.controlcount
   this.controls(i).jt_resize()
endfor
JT_Resize Event

The code that executes in the jt_resize method is similar in nature to the init code, with the exception of setting the "rel" values, we set the actual values of height, width, top, left, and fontsize (where applicable). The code looks like this:

IF UPPER(this.parent.baseclass) <> "PAGE" .and.;
 upper(this.parent.baseclass) <> "Column"
   this.height = this.rel_height * this.parent.height
   this.width = this.rel_width * this.parent.width
   this.top = this.rel_top * this.parent.height
   this.left = this.rel_left * this.parent.width
   this.fontsize = this.rel_fontsize * this.height
   ELSE
   this.height = this.rel_height * this.parent.parent.height
   this.width = this.rel_width * this.parent.parent.width
   this.top = this.rel_top * this.parent.parent.height
   this.left = this.rel_left * this.parent.parent.width
   this.fontsize = this.rel_fontsize * this.height
ENDIF
Form Level Implementation

First, we must create a form class definition, called jt_form. This can be done in any of the acceptable VFP fashions (Visual Class Designer, programatically, Save As Class from form designer, etc.). In the resize event, we place the code from above. Now our form is ready to call the jt_resize method of any object placed on it. It is important to remember that a control placed on the form must have a jt_resize method, or an error occurs.

Control Level Implementation

Next, we must create custom class definitions for each of the objects that we wish to place on our form. Lets begin by creating a custom textbox class called jt_textbox.

From the Class menu selection select add property, and add the properties rel_height, rel_width, rel_top, rel_left, and rel_fontsize. On the property sheet, initialize all of these properties to 0, by replacing the default value of ".F.". Next add a custom method called jt_resize to the form.

In the init code of our class definition, we insert the init code from above. Since textboxes contain fonts, we will include the sections for setting the rel_fontsize property.

In the jt_resize method, place the jt_resize code from above. At this point, we are ready to test our form.

Try it Out!

From the "Tools" menu, select options. Under the forms tab, set the base form to our new jt_form. From the command window type modify form jt_test1 to call up the form designer. Next, add the visual class library in which you stored the jt_textbox class definition to the Form controls toolbar (click on the books, select add, and locate the visual class that we created). Place a
jt_textbox or two on the form, save and run the form. Type some values into the textbox(s), and try to resize the form. You should notice that the size, position, and fontsizes of the textboxes remains relative to the size of the form. Presto, a resizeable form.

At this point, you can go back and create class definitions for all classes, or simply create them as you need them. But, remember, every object placed on a jt_form, must contain a jt_resize method, or you will get an error.

Special Considerations - Container objects

Container objects (forms, grids, pageframes, option groups, command groups etc.) must be handled in a slightly different manner than ordinary controls. Containers must call the resize methods of each object that they contain. The resize method for the jt_form is one approach to ensuring that containers call the jt_resize methods for each of their objects. Outlined below are some of the considerations for dealing with
container objects.

Pageframes

The hierarchy of pageframes can be a little confusing because the only objects that pageframes can contain are pages. Pages cannot be visually subclassed and therefore, we cannot add our custom class to the pages on a pageframe. As such, we must call all of the jt_resize method for each object contained on the pages directly from the pageframe jt_resize method. The code for the pageframe jt_resize method appears below.

Local i, n
<i>* since pageframes can be nested, we must determine if this
* pageframe resides in another pageframe.</i>
if upper(this.parent.baseclass) <> "PAGE"
   this.height = this.rel_height * this.parent.height
   this.width = this.rel_width * this.parent.width
   this.top = this.rel_top * this.parent.height
   this.left = this.rel_left * this.parent.width
   else
   this.height = this.rel_height * this.parent.parent.height
   this.width = this.rel_width * this.parent.parent.width
   this.top = this.rel_top * this.parent.parent.height
   this.left = this.rel_left * this.parent.parent.width
endif
<i>* now iterate through all of the objects contained in all ofthe pages on the pageframe.</i>
for i = 1 to this.pagecount
   for n = 1 to this.pages(i).controlcount
      this.pages(i).controls(n).jt_resize()
   endfor
endfor
Grids

Grids, like pageframes, are limited in what they can contain, namely columns. Columns, however, cannot be visually subclassed. As such, we must handle all resizing of columns and the objects they contain from the grid level.

To assist in maintaining our column width relationships, we will add an array property to the grid, called rel_cols[], which will hold the relative width sizes of each column respectively. The init event code appears below.

Local i
if upper(this.parent.baseclass) <> "PAGE"
   this.rel_height = this.height/this.parent.height
   this.rel_width = this.width / this.parent.width
   this.rel_top = this.top / this.parent.height
   this.rel_left = this.left / this.parent.width
   this.rel_fontsize = this.fontsize / this.height
   for i=1 to this.columncount
      rel_cols[i] = this.columns(i).width / this.width
   endfor
   else
   this.rel_height = this.height/this.parent.parent.height
   this.rel_width = this.width / this.parent.parent.width
   this.rel_top = this.top / this.parent.parent.height
   this.rel_left = this.left / this.parent.parent.width
   this.rel_fontsize = this.fontsize / this.height
endif
In order to implement resizing from the grid level, we will institute a looping construct which iterates through the contained columns, the headers, and the objects contained within the columns. If controls other than the default textbox are used in the grid, we will ignore the jt_resize method, and rely on the grid to maintain their relative positions. The JT_RESIZE code for our custom grid class appears in figure x.
local i, n
<i>* test to see if the user has resized the widths of the columns,
* and maintain their new relative positions.</i>
for i=1 to this.columncount
   this.rel_cols(i) = this.columns(i).width / this.width
endfor
if upper(this.parent.baseclass) <> "PAGE"
   this.height = this.rel_height * this.parent.height
   this.width = this.rel_width * this.parent.width
   this.top = this.rel_top * this.parent.height
   this.left = this.rel_left * this.parent.width
   this.fontsize = round(this.rel_fontsize * this.height,0)
   else
   this.height = this.rel_height * this.parent.parent.height
   this.width = this.rel_width * this.parent.parent.width
   this.top = this.rel_top * this.parent.parent.height
   this.left = this.rel_left * this.parent.parent.width
   this.fontsize = round(this.rel_fontsize * this.height,0)
endif
<i>* reset the column widths based on the values stored in rel_cols[]</i>
for n = 1 to this.columncount
   this.columns(n).width = this.width * this.rel_cols[n]
endfor
Option Groups

There are two methods for dealing with option groups. Option groups, like pageframes and grids hold only options. The primary difference, is that options can be visually subclassed. This means that we can either handle resizing from the option group level, in much the same fashion that we have done for pageframes and grids, or, we can subclass an option, and place it in a container. We can then add our custom properties and method to our option subclass, as well as the container and have them behave as most of our other controls behave.

The choice is left to the developer as to which method they would like to use. Listed below is the code to be placed in the containers init and jt_resize methods. The init and jt_resize methods for the option buttons, is the same as for our other controls.

Init method:

if upper(this.parent.baseclass) <> "PAGE" .and. ;
Upper(this.parent.baseclass) <> "COLUMN"
   this.rel_height = this.height / this.parent.height
   this.rel_width = this.width / this.parent.width
   this.rel_top = this.top / this.parent.height
   this.rel_left = this.left /this.parent.width
   else
   this.rel_height = this.height / this.parent.parent.height
   this.rel_width = this.width / this.parent.parent.width
   this.rel_top = this.top / this.parent.parent.height
   this.rel_left = this.left /this.parent.parent.width
endif
<i>* Set the initial option selected to option 1</i>
this.controls(1).value = 1
jt_resize method
local i
if upper(this.parent.baseclass) <> "PAGE" .and. ;
 Upper(this.parent.baseclass) <> "COLUMN"
   this.height = this.parent.height * this.rel_height
   this.width = this.parent.width * this.rel_width
   this.top = this.parent.height * this.rel_top
   this.left = this.parent.width * this.rel_left
   else
   this.height = this.parent.parent.height * this.rel_height
   this.width = this.parent.parent.width * this.rel_width
   this.top = this.parent.parent.height * this.rel_top
   this.left = this.parent.parent.width * this.rel_left
endif
for i=1 to this.controlcount
   this.controls(i).jt_resize()
endfor
Another consideration for this approach involves maintaining the mutual exclusive behavior of the option buttons. That is, if one is selected, by default, none of the others may be selected. This is handled in the click event of the option button class.
this.parent.setall('value',0)
this.value = 1
The downside of utilizing this approach is that containers, by default, cannot have objects, other than those in the class definition, added to them. That means that if you require an option group with other than the default 2 options, you will need to create an additional class definition. In building a class library, however, you could certainly have a number of optiongroup classes (i.e. optiongroup_1, optiongroup_2, ...optiongroup_n).

Command Button Groups

Commandbutton groups, like option groups have the same limitations and options for handling resizing. The resizing can take place from the CommandGroup level, or by creating custom container classes which contain the buttons that we have already created. The same problem exists, however, of having to have numerous classes to handle the number of command buttons that you wish (i.e. CmdGroup_1, CmdGroup_2, or, CmdGroup_nav, CmdGroup_Edit, etc).

Conclusion

The beauty of VFP, is that it gives developers many options for handling virtually everything. As such, there will almost always be more than one "right answer" to nearly any programming question. I have seen recommendations by VFP programmers that you can handle resizing in the refresh method of the form, for example. While this will certainly work, I can offer 3 reasons why this is a better alternative.

  • In the OOP world, every object should be able to take care of itself. By creating class definitions that handle their own resizing, we stay within the OOP philosophy.
  • The refresh method of a form is called on more occasions than just during a resize. Handling resizing in Refresh will add excessive overhead to your applications.
  • Having each object handle its own resizing needs, eliminates a tremendous amount of code and maintenance. If each class definition is subclassed from the resizeable definition, you will never need to worry about handling resizing again. If a problem occurs, fix it once, and it is fixed everywhere. (The beauty of encapsulation).
The only irregularity that I have noticed with this method of resizing is that if foxels are used instead of pixels in the scale mode, the fontsize will sometimes get out of whack. But, by using pixels as the scale mode, though, I have never had a problem with whacky fontsizes.
The implementation of resizeable forms does require a fair amount of work up front, but, once in place, every form that you create with a sizeable border is automatically resizeable. This will give your applications a polished professional look, and if properly implemented, will eliminate the concern for user monitor resolutions. Simply ensure that your start-up form size will fit comfortably on a 640x480 resolution
display, and let your users resize your forms at will.
John Adams, John T. Adams Software Consulting Svcs