Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Programming Articles  |  Beyond Help Articles  |  Tips & Tricks Articles  


Impossible ObjectPAL Techniques
Introduction and Modifying a Form
© 1994 - 2002 Mark Pauker

Important Note From The Editor

Recently, this article was graciously forwarded to the community (via Vladimir Menkin) by Mark Pauker. However, the article was written back in 1994 and the download includes files dated from 1993 through 1996. It's quite possible that the things you read below will not work under later versions of Paradox and/or with later versions of the BDE and Windows. Please test the concepts below in test environments and not on real/live data or code!

Thanks Mark for everything you've contributed to the Paradox community!


Introduction

Almost everyone who has spent time programming Paradox for Windows has at some point run across something that they could not figure out how to do. When this happens, many of us turn to our local colleagues for suggestions. Failing this, we post our questions to CompuServe or call Paradox technical support. Hopefully this chain of events proves fruitful, and we are provided with ways of accomplishing the required tasks. Unfortunately, no product, no matter how well crafted, can be expected to do absolutely everything. Thus there are times when the final answer to an inquiry is simply: "You can't do that with Paradox."

Having said that no product can be expected to do everything, let me also say that most products can do far more than we think. More often than not, tasks that we consider to be "impossible" are probably better labeled "improbable". I created the Data Entry Toolkit for Paradox DOS in 1986, after compiling a list of over a dozen things that were "impossible" in version 1.1. With the introduction of the toolkit, all of the items on that list became possible.

Paradox for Windows presents its own unique problems, and a very different list of "impossibilities". Nonetheless, the process of figuring out how to accomplish these tasks has not changed much over the years. I have found that the principle ingredients required in stretching a product's envelope are creativity and time, although not necessarily in that order. (It also helps to have a bit of a stubborn streak.) What follows is a set of routines that accomplish tasks which, at some point, I was told were impossible in Paradox for Windows.


Modifying a Form

One of the first things that an ObjectPAL programmer learns is how to modify a form. The statement:
self'color = Red
physically modifies the form on the fly, causing the current object to turn red. Being that this is one of the first things that most people learn, why are we spending time discussing how to modify a form? The reason is that there are several different ways that a developer might want to modify a form, and modifying the property of an object is just the beginning.

For our first example, let's consider a form that can add an object to itself. ObjectPAL provides us with a create method, so that part is pretty straightforward. Once the object is created, however, how can we add functionality to it? Again, ObjectPAL provides a methodSet method so that part is pretty easy too, right? Well, not exactly.

The problem is that methodSet only works when the target form is in design mode. Your first attempt to add code to an object on the current form might look something like this:
ui.create(BoxTool, 1000, 1000, 1000, 1000)
action(DataDesign)
ui.methodSet("mouseClick",
  "method mouseClick(var eventInfo MouseEvent)\r\n\tself'color = Red\r\nendmethod")
Unfortunately, while this code would create a box on the form, it would not place the code in the mouseClick method. The request to go into design mode (in line 2) would not be acted upon until the method terminated, and so the form would still be in "View" mode when methodSet was called, causing it to fail.

Further analysis shows that this code would not have worked even if Paradox had put the form into design mode immediately upon encountering the action method. The reason for this is that all code on the form stops running when the form is placed into design mode, and thus the methodSet method would never have been called.

The Additional Form Approach

Since a form must be in design mode in order for methodSet to operate on it, and no code on the form can be running while it is in design mode, it seems clear that an additional form is required in order to achieve our goal. For this approach, a second form (known as the installing form) is used which places the first form into design mode, modifies it, and then places it back in run mode. My first pass at this looked something like this:
;This code is on the form to be modified, where the variable "ui" has
;already been bound to the object that is to receive the new code
uses ObjectPAL
  placeCode(var fmTarget form, var ui uiObject)
endUses
method mouseClick(var eventInfo mouseEvent)

  ...

  fmTarget.attach()
  fm.open("Install")
  fm.placeCode(fmTarget, ui)
endMethod

;This method is on the installation form, and is responsible for updating the
;code of the appropriate object on the other form

method placeCode(var fmTarget form, var ui uiObject)
  fmTarget.design()
  ui.methodSet("mouseClick",
    "method mouseClick(var eventInfo MouseEvent)\r\n\tself'color = Red\r\nendmethod")
  fmTarget.run()
  close()
endMethod
This code seems to get us closer than the earlier example, but for the same reason described above, it too won't work. The problem is still that the form to be modified will not go into design mode until its method finishes executing. While at first glance it may look like the mouseClick method terminates before the install form's placeCode method is invoked, it really doesn't. Logically, methods end when they reach their endMethod statements. The mouseClick method is in the process of executing the statement:
fm.placeCode(fmTarget, ui)
during the entire time that the install form's placeCode method is running, and thus cannot be placed into design mode. The statement:
fmTarget.design()
in the beginning of the placeCode method is perfectly valid (and will return True denoting success), but the target form will not actually be placed into design mode until after the mouseClick method finishes executing (which in turn will only happen after the placeCode method terminates). Since the target form must already be in design mode in order for methodSet to work, an error will occur when attempting to attach the code to the designated object.

Chaining a Process Using postAction

In order for the above code to work, the mouseClick method must completely terminate before placeCode is invoked. But if mouseClick finishes, how does placeCode ever get called? The answer is to use one of ObjectPAL's most powerful (yet underutilized) methods: postAction. postAction's functionality can loosely be defined as follows: "Allow the current process to finish completely. Once this has occurred and the system is idle, send the specified action to the appropriate object."

The completed, functional code looks like this:
;This code is on the form to be modified, where the variable ui has
;already been bound to the object that is to receive the new code
uses ObjectPAL
  placeCode(var fmTarget form, var ui uiObject)
endUses
method mouseClick(var eventInfo mouseEvent)

  ...

  fmTarget.attach()
  fm.open("Install")
  fm.placeCode(fmTarget, ui)
endMethod

;The following methods are on the form level of the installation form, and are
;responsible for updating the code of the appropriate object on the other form

var
  fmTarget form
  uiTarget uiObject
endVar

method action(var eventInfo actionEvent)
  if eventInfo.id() = UserAction then
    fmTarget.design()
    uiTarget.methodSet("mouseClick",
      "method mouseClick(var eventInfo MouseEvent)\r\n\tself'color = Red\r\nendmethod")
    fmTarget.run()
    close()
  endIf
endMethod

method placeCode(var fm form, var ui uiObject)
  fmTarget.attach(fm'title)
  uiTarget.attach(ui)
  self.postAction(UserAction)
endMethod
In this example, the mouseClick method on the target form opens the installation form and calls the installation form's placeCode method, passing it a handle to itself and to the object to be modified. placeCode simply binds the variables that are passed to it to global variables in the form (so the action method will be able to see them later on), posts an action, and returns. After placeCode completes, the mouseClick method on the target form terminates and both forms are idle.

Once everything is idle, the user action that was posted within the placeCode method will be issued to the installation form's action method by Paradox. The action method's attempt to place the target form into design mode will succeed immediately because no code on the target form is currently executing. With the target form in design mode, the methodSet will succeed, the form will be placed back into run mode, and the installation form will close itself.

Now that we have solved the problem of allowing a form to modify its own code, let's take a moment to look at some of the issues involved in this technique. First, note that this solution requires that we go into design mode. There is no way around this because the new code must be compiled with respect to the existing code (respecting existing scoping rules, etc.), and compiling only occurs at design time. The ramifications of switching into design mode can be significant because doing so causes the form's close methods to fire. Another problem is that the form will lose track of information regarding the current record, locking, etc. After the target form has been updated, the installation form will restart it, which will cause all of its open methods to fire once again. Although these issues require consideration, they are likely to be significant in only a small number of forms, and are reasonably easy to program around as long as we know about them.

One other note: This technique could be implemented using a timer event instead of postAction, but postAction is a more efficient and stable way to accomplish the task. The reason is that the posted action is guaranteed to be invoked immediately after the current event is finished processing, when the target form can be immediately placed into design mode. The timer will fire regardless of whether or not the form can be placed immediately into design mode, and thus additional checks would have to be implemented. Also, the timer method would not be invoked at the instant that the target form was ready to be placed into design mode. Thus it is less efficient, and is prone to interference by user interactions. (A user might click on another object after the process was started, but before the timer fired.)

The Single Form Approach

Before we move on to our next topic, let's take a moment to refine our technique one more time. We have seen that, in order to allow a running form to cause itself to be modified, a second (installation) form is required. But what if we don't want to use a second form? The final technique we will cover allows a form to modify itself without the use of an installation form. Better still, we will create a clip-object that will allow any form to modify its own code.

If, given what has already been presented in this paper, you think that it is impossible for a form to modify its own code without help from another form, you are absolutely right. So how do we do it? Simple... We cheat! Instead of using a completely different form to do the modification, we open a second instance of the same form. Thus a single FSL can be used to modify the currently running instance of itself. You will notice that the code used to accomplish this task is almost identical to that which has already been presented, except that it has been further enhanced to be a somewhat generic clip object. The potentially confusing aspect of this code is in trying to keep track of which method is being called from what instance of whose form. The code is all contained by a single object, but we will look at the methods broken down by the instance that uses them:
method newMethodSet(const stObjectName string,
                    const stMethodName string,
                    const stMethodText string)
  var
    fmInstaller,
    fmTarget form
    uiInstaller uiObject
  endVar
  fmTarget.attach()
  fmInstaller.open(getFileName(), WinStyleDefault + WinStyleHidden)
  fmInstaller'title = " " + fmTarget'title + " "
  uiInstaller.attach(fmInstaller, self'fullName)
  uiInstaller.placeCode(fmTarget, stObjectName, stMethodName, stMethodText)
endMethod
The above method is called by the form that wants to modify itself. It is passed the name of the object that we want to add code to, the name of the method, and the actual code to place in that method. newMethodSet opens an additional copy of the form that contains it and changes the title of that copy (otherwise we might not be able to attach back to this form from the new instance that we are opening). The next thing that happens is that newMethodSet determines the name of the object that contains it, attaches to that object's counterpart on the other instance of the form, and calls the placeCode method in that object on that form.

At this point, the second instance (which is analogous to the installation form that we have discussed above) takes over. The code used by the second instance is as follows:
var
  fmTarget form
  uiTarget uiObject
  stMethod,
  stCode string
endVar

method placeCode(var fm form,
                 const stObjectName string,
                 const stMethodName string,
                 const stMethodText string)
  fmTarget.attach(fm'title)
  uiTarget.attach(fm, stObjectName)
  stMethod = stMethodName
  stCode = stMethodText
  self.postAction(UserAction)
endmethod

method action(var eventInfo ActionEvent)
  if eventInfo.id() = UserAction then
    fmTarget.design()
    uiTarget.methodSet(stMethod, stCode)
    fmTarget.run()
    close()
  endIf
endmethod
The placeCode method receives a handle to the form being modified, the name of the object being modified, the name of the method to add/modify, and the text of the new code. Its first task is to create global variables for each of these arguments so the action method will be able to use them. It then posts an action to itself which will eventually cause the other form to be modified (as described earlier). Note that the variables in the Var window are not available to the form that is being modified, as that form is a different instance with a completely different set of variables.

Defining Radio Button and Checkbox Fields

The other area we will look at under the topic of modifying a form is creating radio button fields and checkbox fields. At first glance this seems simple enough-- simply create a field and change its displayType property:
ui.create(FieldTool, 1000, 1000, 1000, 1000)
ui'displayType = RadioButtonField
So far so good. The problem occurs when you attempt to further define the radio buttons. How do you do it? Interactively, when you set the displayType property to radio buttons, a dialog box appears allowing you to define the number of buttons and the values for each button. When setting the property through program control, however, there is no way to do this. A similar problem occurs with checkboxes, as there is no way to specify the value when checked or the value when unchecked. To solve these problems, you can create the field using the create method, and then use sendKeys to interactively supply the values necessary to the dialog box. In order to use this technique, though, the target form must first be placed in design mode.

Code that will allow a form to switch itself into design mode, do something, and then switch back into run mode has been described in the sections above. Thus we have a way to fully define radio button and checkbox fields on the fly. Nonetheless, switching into design mode has many undesirable side effects, and using sendKeys will cause a dialog box to flash on the screen. A much better solution would allow us to completely define these fields without having to go into design mode.

The key to a non-design-mode solution was provided by Jim Kocis, who observed that when a field object's displayType property is changed from one type of compound object to another, the information already known about that object is preserved to the best of Paradox's ability. Thus when a checkbox field is changed to a radio button field, the values of the radio buttons default to the values that had been defined for the checked and unchecked states of the checkbox. So how does this information help us? There is one compound object that we have complete control over: the list. By defining a list and then changing its displayType to a different object, we will have completely defined that other object. The following code illustrates how to completely define a radio button field:
method makeRadio()
  var
    uiField, uiList uiObject
  endVar
  uiField.create(FieldTool, 1000, 1000, 0, 0)
  uiField'displayType = ListField
  uiList.attach(uiField'first)
  uiList'list.selection = 1
  uiList'list.value = "Small"
  uiList'list.selection = 2
  uiList'list.value = "Medium"
  uiList'list.selection = 3
  uiList'list.value = "Large"
  uiField'displayType = RadioButtonField
  uiField'visible = True
endmethod
In this method, we first create the field object. Its width and height are inconsequential because changing the displayType will cause the field to resize anyway. The next thing we do is to make the field a list field. This will cause it to contain a list object that we can further define. After defining three items, we change the displayType to radio buttons, and Paradox does the rest of the work for us. Defining a checkbox field is done in a similar manner:
method makeCheckBox()
  var
    uiField, uiList, uiButton, uiLabel uiObject
  endVar
  uiField.create(FieldTool, 1000, 1000, 0, 0)
  uiField'displayType = ListField
  uiList.attach(uiField'first)
  uiList'list.selection = 1
  uiList'list.value = "No"
  uiList'list.selection = 2   ;For checkboxes, value when checked
  uiList'list.value = "Yes"
  uiField'displayType = CheckBoxField
  uiButton.attach(uiField'first)
  uiLabel.attach(uiButton'first)
  uiLabel'designSizing = TextSizeToFit
  uiLabel'text = "Checked?"
  uiField'visible = True
endmethod
The only real difference here is that you need to know which list item describes the "checked" value, and which describes the "unchecked" value. There is also a bit of additional code that changes the labeling of the field, but that should be pretty straightforward.


Programming Reports


Discussion of this article


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 15 May 2003
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2003 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.