UBBFriend: Email This Page to Someone!
  Computer Workshop User Forums
  Online Lessons in using EZGUI
  Step-bystep Guide to Making Re-sizable Apps

Post New Topic  Post A Reply
profile | register | preferences | faq | search

next newest topic | next oldest topic
Author Topic:   Step-bystep Guide to Making Re-sizable Apps
JoeByrne
Member
posted 05-08-2009 07:58 PM     Click Here to See the Profile for JoeByrne     Edit/Delete Message Reply w/Quote
(This is the first of a 2-3 part series on how to create re-sizable EZGUI programs. Part 2 will be posted soon. Please post comments or questions in the appropriate thread on this forum)
============================================

Like any great tool, EZGUI is capable of doing more things than the average programmer may be inclined to explore. One thing that I've always wanted was to make my program 'stretchable'. It seems that most Windows applications can be easily resised to fit the viewer's preference. On the surface, this seems like it should be easy enough to do, but without spending quite a bit of time to learn all the nuances of 'elasticity', it can quickly become one of those things that "you'll get to later" which may, or may not happen in this lifetime.

I finally decided to tackle this feature on a project I've worked on-and-off again for the past several years. Since this is a personal project, I wasn't under the gun to get any part of it completed on schedule. Now that I have the code working, it is easier to see the big picture and realize that, once again, EZGUI makes the process quite EZ.

The goal is to take an application (usually generated by the EZGUI designer) and allow the user complete control over its size and position. There are a couple of things to consider before starting out.


  1. Some controls simply look awful when expanded too far.
  2. Sometimes a great looking design can get all out of wack when the form is resized.
  3. Its not enough to allow resizing, its equally important to restore the application to its new size when its been restarted.
  4. We don't want to get too specific to any piece of code. The process should be easily transported to other projects.

Ok, so lets take a look at what's necessary. The first step is to allow the form, and all the controls on it, to be resized by dragging the boarders around. This is actually quite easy and does not take a lot of code to accomplish. (For clarification, I will refer to the FORM name in all my examples as "MAIN".)

To begin with, you must add the "Z" property to the form in the EZ_MAIN_DISPLAY SUBroutine. The Z property add a 'drag handle' to the lower right corner and allows the frame of the form to be moved in any direction. Adding just the 'Z' property will allow the form to be resized but the controls contained on the form will not adjust to the new size. To accomplish this task we need to track the resize changes, calculate the required size changes of every control, and then physically change the size of our controls. Trust me here, its easier than it sounds.

We'll keep track of the control positions with a GLOBAL array: Someplace before SUB EZ_MAIN add the line:
code:



GLOBAL ResizeInfo!()


Next we need to get the starting position and size of our form and all it's controls. This is done with the following subroutine:
code:

SUB InitResizeInfo(BYVAL FORM$)
'===================================================================================
' Get the Original Sizes of the form/controls
'-----------------------------------------------------------------------------------
LOCAL MaxC&, N&, CN$, CC!, CR!, CW!, CH!, ID&

EZ_STARTCLIST FORM$, "" '---> Enumerate All Controls on the form by class name
MaxC& = EZ_GETCLISTCOUNT
EZ_ENDCLIST
'
REDIM ResizeInfo!(1 TO MaxC&,1 TO 4) '--> (Col, Row, Width, Height)
EZ_STARTCLIST FORM$, ""
MaxC& = EZ_GETCLISTCOUNT
FOR N&=1 TO MaxC&
ID& = EZ_GETCLISTID(N&) '--> Get the EZGUI ID number for an enumerated control on a form

EZ_GETSIZEC FORM$, ID&, CC!, CR!, CW!, CH! '---> Get the position and size of a control (column, row, width and height).
ResizeInfo!(N&,1) = CC!
ResizeInfo!(N&,2) = CR!
ResizeInfo!(N&,3) = CW!
CN$ = EZ_GETCLISTCLASS(N&,1) '---> Get the class name for an enumerated control (BUTTON, TREEVIEW, Etc.)
IF CN$ = "COMBOBOX" THEN
CH! = CH! * 5 ' height returned for combobox is only height of visible part
END IF
ResizeInfo!(N&,4 )= CH!
NEXT N&
EZ_ENDCLIST
END SUB


We need to CALL this routine immediately after the form is created, before the user can make any changes. There are two places where this can be done, either as the last line in the SUB EZ_MAIN_Design() routine, or in the SUB MAIN_Events. The latter option is my recommendation as the EZ_MAIN_Design code block is protected area for the designer. In the MAIN_Events block, put the call to InitResizeInfo right after the CASE %EZ_LOADED test.

InitResizeInfo is only called once to create our Global array with the starting positions of every control on the form. It begins by enumerating all the controls by CLASS NAME only to get a count of the number of controls to deal with in order to properly DIMention the array. Next the actual EZGUI Control ID number is retrieved (EZ_GETCLISTID) to be used by EZ_GETSIZEC. EZ_GETSIZEC gets the current screen position (CC! and CR!) of the control, its width (CW!) and its hight (CH!). We simply store these values for later reference. One caveat is the COMBOBOX. In order to resize it properly, we need to factor in the invisible 'drop-down' portion. In order to identify a Combobox on the form, we need to check the control's Windows Class which we do with the EZ_GETCLISTCLASS command. This command returns the controls class in upper case letters.

The next thing we need to do is recognize when the form it being resized. This is easily done by adding a test in the MAIN_EVENTS subroutine like this:
code:



SUB MAIN_Events(CID&, CMsg&, CVAL&, CANCEL&)
'===============================================================
'Process Form and control events on MAIN form
'---------------------------------------------------------------
SELECT CASE CID&
'--- Form Events ---
CASE %EZ_Window
SELECT CASE CMsg&
CASE %EZ_Size
ResizeForm "MAIN"
...


When a form is resized, EZGUI fires off an event using %ES_Size. When this event happens, we make a call to ResizeForm <FORMNAME> which handles the dirty work. This routine looks like this:
code:

SUB ResizeForm(BYVAL FORM$)
'==================================================================
'This routine is called from the Form's Event Sub (Form1_Events).
'It gets the new coordinates of the form and controls and then
'redisplays the form with the changed sizes, keeping track of the
'current positions and sizes in the array ResizeInfo()
'------------------------------------------------------------------
LOCAL W!, H!, NC!, NR!, NW!, NH!, OW!, OH!, ID&, N&, MaxC&, CN$

EZ_SETREDRAWSTATE FORM$,0,0 '--> Do Not redraw form when resized/moved

OW! = 99 '\ Save the width (OW!) and hight (OH!) of Original Form Size.
OH! = 36 '/ Set in SUB EZ_MAIN_Display routine

EZ_GETSIZE FORM$, W!,H!,0 ' get form client area size
'
' define ratio between old size and new
'
OW! = W!/OW!
OH! = H!/OH!

EZ_STARTCLIST FORM$, "" ' list all controls
MaxC& = EZ_GETCLISTCOUNT
FOR N&=1 TO MaxC&
' resize controls based on ratio
NC! = ResizeInfo!(N&,1) * OW!
NR! = ResizeInfo!(N&,2) * OH!
NW! = ResizeInfo!(N&,3) * OW!
NH! = ResizeInfo!(N&,4) * OH!

ID& = EZ_GETCLISTID(N&)

CN$ = EZ_GETCLISTCLASS(N&,1)
IF CN$ = "LABEL" OR CN$ = "TEXT" OR CN$ = "BUTTON" OR CN$ = "DATETIME" THEN NH! = ResizeInfo!(N&,4)

IF CN$ <> "SYSHEADER32" THEN ' ignore listview header
EZ_RESIZEC FORM$, ID&, NC!,NR!,NW!,NH!
END IF
NEXT N&

EZ_ENDCLIST
EZ_SETREDRAWSTATE FORM$,0,1 '--> Re-allow form redrawing
EZ_REDRAWFORM FORM$ '--> Redraw with new sizes
END SUB



This is pretty straight forward. First we prevent the form from resizing during this sub using EZ_SETREDRAWSTATE setting the last parameter to 0. Next we take the original height (OH!) and width (OW!) of our form and the new width and height to calculate the difference that each control needs to adjust them correctly on the form. Finally, we manually resize each individual control using EZ_RESIZEC and clean things up. OH! and OW! are provided to us from the designer in the EZ_FORM command when the form is first created.

Remember though, that certain controls might not look so good when they change sizes, especially their height. For example, if you expand a textbox you'll either need to also use a bigger font, or live with a lot of extra white space on the control. How you deal with these controls is strictly up to you. In my opinion, I think labels, textboxes, buttons, and the datetime controls are best left at their original height. Therefore, by testing each control's CLASS name, I can eliminate height changes to selected control types, such as the textbox. Also, we don't want to change the headers on the LISTVIEW control so that is tested for separately. The final line performs the actual redraw of our form with each control correctly adjusted to match the new shape of the form.

At this point, you have all the components to make your applications truly 'elastic'! Go ahead and plug these items in and you'll see that you can pretty much stretch your form anyway you want and the controls will adjust as well!

One thing we probably want to include though. As is, the form can be shrunk to nothing but the title bar. This might be ok, but normally a form should only resize downward to its original size. To accomplish this, simply add the command:
code:



EZ_SETFORMMINMAX "MAIN", w!,h!,999,999


immediately following the EZ_FORM command in the EZ_MAIN_Display routine where your form is created. The values for w! and h! represent the minimum width and height of the form. These values are automatically set for you from the designer in the EZ_FORM command.

In the next part I'll show you the modifications necessary to make your form "remember" the exact size and shape it was the last time the program ended so you can maintain the exact form design whenever the program is run. I'll also show you some techniques I used to get around some placement issues with forms that are designed to 'line-up' just right.

[This message has been edited by JoeByrne (edited 05-08-2009).]

JoeByrne
Member
posted 05-09-2009 02:35 AM     Click Here to See the Profile for JoeByrne     Edit/Delete Message Reply w/Quote
Resizing Forms and Controls -- Part 2 --

In part one we covered the basics of resizing the controls on a form. This is fairly standard for any modern Windows program so its a method we'd be well to incorporate. A big aspect of 'user customized' form sizes though, is to remember how the user left the form and restore it to that state the next time it starts up. Fortunately, this too is not terribly difficult to accomplish.

The basic concept is simple. When the program ends, we want to save the current position and side of the form and all of it's controls. There are two logical places to store this information, in an .INI file or the registry. I personally prefer .ini files which is what I'll use for this example, but you can just as easily store the information in the registry using EZGUI's registry functions.

This process does create a major problem with EZGUI 4 though. As you will see, we need to modify the sections of the EZGUI form design code which is off-limits if you use the re-entry features of the designer. Should you go back to the designer, make changes and then re-generate your code, the changes we need to make will be lost. Therefore, you have to decide ahead of time how you want to deal with that. You can save this step until the very end, but you'll have to remember the potential problem in the future if you want to make changes using the designer. I started out with EZGUI version 2 and spent a great deal of time hand-modifying my source code for any changes that took place after the initial design code was generated. I still find this method faster for me, therefore I don't have to worry about the re-entrant issue, but its something to be aware of.

Ok, lets start with the end We need to save the state of the form when the program is closing down. Fortunately, this is a very simple process. When an EZGUI program is ending, regardless of what method the user takes, a jump is made to the MAIN_Events subroutine. (Remember from part one that I am using the name MAIN as my primary form's name. You should substitute your form's name wherever you see MAIN in this sample code.) In the SELECT/CASE structure in MAIN_Events, there is a test for %EZ_Close. This is where we will put our code to save the current state of the form. Your routines to access INI files might differ from my samples, but simply substitute the commands you need to read and write INI files, or use the registry. The code for saving the necessary information is as follows:
code:



CASE %EZ_Close
'--- Save the screen position ---
IF INI_GetKey(gINIFile,"OPTIONS","RS","") = "Y" THEN
INI_DELETEKEY(gINIFile,"OPTIONS","RS")
ELSE
EZ_GETPOS $MAIN, C!, R!, 0
INI_SETKEY(gINIFile,"PREVSTATE","C",FORMAT$(C!))
INI_SETKEY(gINIFile,"PREVSTATE","R",FORMAT$(R!))
' --- Save Form Size ---
EZ_GETSIZE $MAIN, W!, H!, 0
INI_SETKEY(gINIFile,"PREVSTATE","W",FORMAT$(W!))
INI_SETKEY(gINIFile,"PREVSTATE","H",FORMAT$(H!))
'
'--- Save Last Position of each control ---
'
InitResizeInfo($MAIN)
EZ_STARTCLIST $MAIN, ""
FOR N& = 1 TO UBOUND(ResizeInfo!())
ID& = EZ_GETCLISTID(N&)
cPOS$ = FORMAT$(ResizeInfo!(N&,1)) + "," + _
FORMAT$(ResizeInfo!(N&,2)) + "," + _
FORMAT$(ResizeInfo!(N&,3)) + "," + _
FORMAT$(ResizeInfo!(N&,4))
INI_SETKEY(gINIFile,"PREVSTATE",FORMAT$(ID&),cPos$)
NEXT
EZ_ENDCLIST
END IF
EZ_UNLOADFORM "DEBUG"
CASE ELSE


I will explain the need and use of the "RS" key in the next section so just ignore it for now. Following the ELSE clause, we first save the form's physical location on the screen using EZ_GETPOS. This gives us the screen column (C!) and Row (R!) of the form in character measurements (the 3rd option being zero). This is all we need to reposition the form exactly where it was the next time. In the INI file, I put all of these settings in a section called "PREVSTATE" although you can use any section name you wish.

Next we save the size of the form using EZ_GETSIZE for it's Width (W!) and Height (H!) again in character units by setting the last parameter to zero.

Finally we will enumerate the controls and save their coordinates one by one. I start by calling the InitResizeInfo subroutine which just makes sure that our global array ResizeInfo!() holds the most current values for each control's size and position. Its a simply matter then to store the Column, Row, Height, and Width for each control using it's ID. In order to minimize the number of lines required to save these four values for each control, I concatenate them into a comma-separated-string as: "C!,R!,W!,H!" (Column, Row, Width, Height). Using the same Section "PREVSTATE", I use the control's ID value as the INI key. If you look at this section in the INI file, it would look something like this:

code:



[PREVSTATE]
C=55
R=12.3125
W=99
H=36
100=27,4.25,71.375,30.125
105=71.625,2.625,3.375,1.4375
110=75.75,2.8125,19.625,1
115=27,4.125,71.375,18.9375
120=71.625,2.625,3.375,1.4375
125=26.625,24.8125,33.25,1.375
130=59.875,24.875,2.5,1.375
...


Where C is the FORM'S SCREEN COLUMN, R is it's ROW, W the WIDTH, H the HEIGHT, and each subsequent entry holding the control IDs followed by their size values (Column, Row, Width, and Height). Even with hundreds of controls, this process is not noticeable when the program ends.

The next step is to use these settings to re-create the form when the program runs again. The bulk of this work is done in the EZ_MAIN_DESIGN and EZ_MAIN_DISPLAY routines. It does however, require a fair amount of copy-and-paste hand coding.

If you look at these two routines, you can see how EZGUI creates the forms and controls. We're going to save the size and position values that the designer generated as our 'default' settings, and replace these constant values with variables which we'll read from our INI file. Its important to save the original values since the form needs to know how to be drawn the first time its run and no sizing values exist in our INI file yet. It is also helpful if the user wants to reset the form to "Factory Defaults".

The process is quite straight forward. Before the EZGUI commands to create the form and controls is issued, we'll look at the INI file for any previous settings for the placement of the form or control. For controls, these settings are the 4 digit sets separated by commas. Since we'll be parsing the same format of numbers for each control, I use this simple Subroutine to minimize coding:
code:



SUB GetCoordinates(BYVAL Ctrl&,cPOS!(),DefPos$)
'==================================================================
REDIM cPOS!(1 TO 4), xArray$(1 TO 4)

x$ = INI_GETKEY(gINIFile,"PREVSTATE",FORMAT$(Ctrl&),DefPos$)
PARSE x$,xArray$()
FOR i& = 1 TO 4
cPOS!(i&) = VAL(xArray$(i&))
NEXT
END SUB

This function takes the Control ID, a blank Array, and our default positions as parameters. It then looks for a key in the INI file using the Control ID value passed and then parses the 4 required values into our size array (cPOS!()). My INI code uses the last parameter as a default value if the key does not exist. If you're INI code doesn't do this, then you'll need to test for the key and if it doesn't exist, substitute the 'default' values passed in the parameter DefPos$. We call this sub immediately prior to creating a control on the form, then using the cPOS!() array values instead of the constants generated by the EZGUI designer. For example, the EZ_Design() block might look something like this:
code:



SUB EZ_MAIN_Design() ' (PROTECTED)
LOCAL CTEXT$
LOCAL hMainMenu&, hDropMenu&, hSubMenu&

DIM cPos!(1 TO 4)
EZ_COLOR-1,-1
EZ_SETLAYER 1
EZ_USEFONT 7
EZ_SUBCLASS 2
GetCoordinates(%MAIN_T1_SOURCE,cPOS!(),"27, 4.25, 71.375, 30.125")
EZ_RICHTEXT2 %MAIN_T1_SOURCE, cPOS!(1),cPOS!(2),cPOS!(3),cPOS!(4), "?ABESTV"
EZ_SUBCLASS 0
EZ_ADDTOOLTIP "MAIN", %MAIN_T1_SOURCE
' -----------------------------------------------
EZ_COLOR-1,-1
EZ_USEFONT 4
STATIC PN1$
IF PN1$ = "" THEN PN1$ = EZ_LOADICON("$sm_switch-pages")
GetCoordinates(%MAIN_T1_SUBSFUNCTIONS,cPOS!(),"71.625, 2.625, 3.375, 1.4375")
EZ_IBUTTON %MAIN_T1_SUBSFUNCTIONS,cPOS!(1),cPOS!(2),cPOS!(3),cPOS!(4) , PN1$, "T"
EZ_ADDTOOLTIP "MAIN", %MAIN_T1_SUBSFUNCTIONS
' -----------------------------------------------
EZ_COLOR 1, 15
EZ_USEFONT 6
GetCoordinates(%MAIN_T1_LABEL1,cPOS!(),"75.75, 2.8125, 19.625, 1")
EZ_LABEL %MAIN_T1_LABEL1,cPOS!(1),cPOS!(2),cPOS!(3),cPOS!(4) , "Show Subs & Functions", "IL"
' -----------------------------------------------
EZ_COLOR-1,-1
EZ_SETLAYER 2
EZ_USEFONT 7
EZ_SUBCLASS 2
GetCoordinates(%MAIN_T2_NOTES,cPOS!(),"27, 4.125, 71.375, 18.9375")
EZ_RICHTEXT2 %MAIN_T2_NOTES,cPOS!(1),cPOS!(2),cPOS!(3),cPOS!(4) , "?AESTVB"
EZ_SUBCLASS 0
EZ_ADDTOOLTIP "MAIN", %MAIN_T2_NOTES
' -----------------------------------------------
...


For every control we create, we first call GetCoordinates passing the ID of the control, our numeric array to be filled, and a comma-separated string containing the position and size values generated by the designer. Everything else in the EZ_MAIN_Design() block remains the same. If you want a challenge, it should be possible to develop an EZGUI plug in for the Designer to parse this section of code and create the necessary changes, otherwise we have quite a bit of hand-editing to do.

Of course we need to create and position the form before we add the controls. That is an easier step since it requires far fewer values. The changes necessary are done in the EZ_MAIN_DISPLAY routine, for example:
code:



SUB EZ_MAIN_Display(BYVAL PARENT$) ' (PROTECTED)
LOCAL hMainMenu&
' Main Menu handle automatically stored by EZGUI
hMainMenu&=EZ_DEFMAINMENU( %MAIN_FILE, "&File", "")
EZ_COLOR -1, -1
' --- Restore last position ---
c! = VAL(INI_GetKey(gINIFile,"PREVSTATE","C","0"))
r! = VAL(INI_GetKey(gINIFile,"PREVSTATE","R","0"))
w! = VAL(INI_GetKey(gINIFile,"PREVSTATE","W","99"))
h! = VAL(INI_GetKey(gINIFile,"PREVSTATE","H","36"))

Prop$ = "_^Z"
IF c! + r! = 0 THEN Prop$ = "_C^Z"
EZ_FORM "MAIN", PARENT$, "CodeClips", c!, r!, w!, h!, Prop$
EZ_SETFORMMINMAX "MAIN", 99,36,999,999
END SUB


First we get the last known settings from our INI file using 'C'olumn, 'R'ow, 'W'idth, and 'H'eight keys. If these do not exist, we set their defaults to the coordinates generated by the EZGUI Designer (99 and 36 in the example above).

Also, when the program is run for the first time (or the user resets the positioning to 'factory defaults') we probably want the form Centered on the screen. This is done automatically for us by using the "C" value in the EZ_FORM property string. However, if this value is used after the user has repositioned the form, the "C" parameter will over-ride the user defined placement which we don't want. Therefore, we set our property string without the 'C' parameter first, then test for the existence of a non-zero value in either the Column or Row positions. If both of these are zero then we'll go ahead and center the form on the screen using the 'C' parameter.

One last thing we might want to do is set a minimum form size which is the size we originally designed the application for. This is done with the EZ_SETFORMMINMAX command. Again, the minimum values are taken from the form size generated from the designer (99 and 36 in the example). You can set MAXimum values as well if you wish, but I generally allow the user to use as much video real estate as they wish.

I also think its a good idea to allow the user to set everything back to its originally designed size. This is optional, and how you want to flag it is up to you. I generally use a menu option such as "Reset to default size", but you can use a dedicated button, a separate configuration form, or any other method you see fit to allow the user to select this option.

To reset the values, we can do one of two things. (1) Change the stored values to our original values (the 'defaults' we pass along to the GetCoordinates subroutine) or (2) just delete the keys from the INI file and let the existing code use the default values. For no particular reason, I normally use option #2. Either way, we create a small sub that edits our INI file and changes or removes the values stored in the "PREVSTATE" section. My INI code has a single call option to remove an entire section at once, but I also save other things here, such as the last file used. Therefore, I normally opt to delete the sizing keys individually something like this:
code:



InitResizeInfo($MAIN)
'
' --- Delete the FORM size/position values ---
'
INI_DELETEKEY(gINIFile,"PREVSTATE","C")
INI_DELETEKEY(gINIFile,"PREVSTATE","R")
INI_DELETEKEY(gINIFile,"PREVSTATE","W")
INI_DELETEKEY(gINIFile,"PREVSTATE","H")
'
' --- Delete the individual Control size/position values ---
'
FOR N& = 1 TO UBOUND(ResizeInfo!())
ID& = EZ_GETCLISTID(N&)
INI_DELETEKEY(gINIFile,"PREVSTATE",FORMAT$(ID&))
NEXT
'
' --- Set the 'reset-to-default' flag ---
'
x& = INI_SetKey(gINIFile,"OPTIONS","RS","Y")
mtext$ = "You must restart the program for changes to take effect"
mTitle$= "Screen Sizes Restored"
v& = EZ_MSGBOX($MAIN,mText$,mTitle$,"OK")


I could redraw the screen at this point, but being somewhat lazy, I make the user restart the program themselves. You may recall that I have an IF/THEN test in the %EZ_Close section that saves the state of the current form. This test is necessary here, when the user resets the form to its default state. If we don't tell the ending code not to save the current settings, then changes (or deletions) to our INI file settings will be lost when the user exits the program. Therefore, I just add a key to the INI file, under "OPTIONS" to flag the closing routine to skip the section to save the current form size. Obviously you can do this in a multitude of ways like a GLOBAL flag, a USER DATA setting, or anything else your heart desires, but for no particular reason, I used the method above.

So here you have it. Using the techniques in parts one and two of this tutorial, you can create Windows applications that can be completely resized by the user and will return to their user-defined size and position each time the program is launched. I think most people will agree that this should be a standard feature for any windows program. After you've digested these little techniques, it should be fairly easy to implement them in all of your EZGUI applications.

In the last part of this tutorial I will show you how I handle some quirks about resizing the ListView control which presents a special challenge.

[This message has been edited by JoeByrne (edited 05-09-2009).]

Chris Boss
Administrator
posted 05-09-2009 03:22 PM     Click Here to See the Profile for Chris Boss     Edit/Delete Message Reply w/Quote
Excellent tutorial Joe.

Thanks very much. Many other EZGUI users will benefit from this.

All times are EST (US)

next newest topic | next oldest topic

Administrative Options: Close Topic | Archive/Move | Delete Topic
Post New Topic  Post A Reply
Hop to:

Contact Us | Computer Workshop ( EZGUI ) Home Page

Copyright 2000 to 2007 Christopher R. Boss

Powered by: Ultimate Bulletin Board, Version 5.44
© Infopop Corporation (formerly Madrona Park, Inc.), 1998 - 2000.