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 cant 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 objects 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 objects parents 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:
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 objects 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 objects 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, orfirsttab, have a baseclass of Page. The revised code to test for parents 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
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 forms resize event appears as follows.
Local i For i = 1 to this.controlcount this.controls(i).jt_resize() endfor
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
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 ajt_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 withcontainer 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, 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
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
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
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
this.parent.setall('value',0) this.value = 1
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.