long ago i promised you that i would give you a series of posts about the work behind profilsmart. this is the first installment of this series and today i will try to give you a deeper look into profilsmart's forms.

usually i try to be very careful when i create a new application. i have always in my mind that what understands a user as "application" is just the user interface (ui) so i am very scholastic designing the ui.
 three years ago i found myself to spend almost half (40% - 45%) of my time creating new forms and playing with gui controls. i don't know whether it was my laziness or the need that made me to create a "strange" form then. playing with the colors or the controls on a form for a corporate team maybe it is a pleasant occupation but when you are the only developer in the company and you have to wear a dozen of hats every day, you have to find solutions and efficient ways to turn your development into a really rapid one.
my very first thought was to create a set of form classes with typical gui controls and functionality that could be inherited and used in every need. very soon a better idea came to mind and it was then that i tested to have a single form as "skeleton" and different container objects for the specific cases. so instead to load different forms across my application i created a "master page" with a pleasant color scheme, the navigation controls while a "sub form" was called according the occassion.

very soon my application in "design mode" was looking like this:

 

...hey this is an empty form! how can this be your 12 months work ???

ok let's dive a bit deeper...

the form has a custom method called caddpanel. the code of the method looks like the listing:
(since profilsmart is a commercial close source application i can't publish the exact code here but i hope you get an idea)

function caddpanel
lparameters tcpanelname

if ! vartype(&tcpanelname)="o" then
 
 if vartype(thisform.cfrmpanel)="c" then
  lcobjectname = "thisform." + thisform.cfrmpanel
  evaluate(lcobjectname + ".onbeforeclose()")
 endif

 thisform.cremoveall()
 thisform.addobject(tcpanelname,tcpanelname)
 lcobjectname = "thisform." + tcpanelname

 this.cfrmpanel = tcpanelname
 with &lcobjectname
  if not .cnoresize
   .top = this.xpanel_guide1.top
   .left = this.xpanel_guide1.left
   .width = this.xpanel_guide1.width
   .height = this.xpanel_guide1.height
  else
   .top = .cmytop
   .left = .cmyleft
  endif

  .cactivate()
  .visible = .t.
  if tnkeyvalue < 0 then
   .caddnew()
  else
   .cprimarykeyvalue = tnkeyvalue
   .crequery()
  endif
 
  .refresh
 endwith
endif

thisform.refresh

as you can see the method accepts the name of the panel (container object) and then loads the container. in the form we have an xpanel_guide1 object (a simple shape obj visible=.f.) that works as a proxy and the container takes its place.

in addition the form has a property called cfrmpanel that keeps the name of the current container.

after the new container's creation the caddpanel function calls a couple of methods for new objects's setup.

 

every container has a property called cnextpanel that holds the name of the next panel. this is easily configurable at design time while the form's next button is clicked the cshownext method is called.

function cshownext
local lcobjectname, lbcheck, lcnextpanel

if vartype(thisform.cfrmpanel)="c" then
 lcobjectname = "thisform." + thisform.cfrmpanel
 evaluate(lcobjectname + ".onbeforenext()")
 if pemstatus(thisform,thisform.cfrmpanel,5)
  with &lcobjectname
   if vartype(.cnextpanel)="c" and not empty(.cnextpanel)
    lcnextpanel = .cnextpanel
    lbcheck = (vartype(lcnextpanel)="c")
   endif
  endwith
 endif
endif

if lbcheck
 thisform.caddpanel(lcnextpanel) 
endif

let's imagine that you have to code a 4 pages wizard form. all you have to do is to create a new form based on the xbaseform class and to fill 4 containers with the appropriate controls. in the first container you write in the cnextpanel property the name of the second panel, in the second the name of the third and so on...
you run the form and your wizard is ready!

continuing to throw here listings with code i am sure that very soon you will leave this page with headache. stand by one minute and let's see what are the benefits of this approach.

 1. it is really easy to have a consinstent user interface accross the whole application.
 2. having solved basic form functionality (resize code, close buttons, navigation controls etc...) your development time is reduced many times.
 3. when you want to make an application wide change (form's layout, form's colors , theme support etc.) you have to change a single point!
 4. from my metrics it is faster to load a container than to call a brand new form at runtime.
 5. you can have more simplistic ui elements in different containers instead of a crowded form.
 
in my own framework the form does not know anything about business objects or data. i don't use form's dataenvironment and the form's bindcontrols property is by default to .f.

the containers have custom methods that handle the data.

this is a typical supplier's form at runtime:

in the next post of the series we will examine the container objects.

see you!

 

 

 

 

 

 

 

 

 

 

 

 

One Response to Programming Visual Foxpro with Master Pages (…in mind)

  • Andrzej says:

    but hey, you have promised a deeper look into “master pages”, and I am still waiting…

    🙂

    Best regards

    Andrzej

Leave a Reply

Your email address will not be published. Required fields are marked *