|
Technical answers from the trenches |
|
Controlling Paradox with Messages
| ||||
by Lance Leonard |
Posted: 1 July 2000 |
|||
  |
All versions |
|||
  |
Audience: Intermediate |
|||
Introduction:As a developer, you may run into situations where you want to automate Paradox from an external program, such as a front-end built using Delphi. While there are a number of different ways to approach this, many have disadvantages that make implementation difficult. For example, OLE Automation can be problematic. DDE is more reliable, but far more limited in scope and not well documented. VBA scripting is a more recent possibility, but comes with its own headaches--especially when deploying an application. You can even use the Paradox comand line, but the load times can be prohibitive for impatient users. There is an easier solution, one that doesn't get much press. By leveraging an underlying behavior of Windows itself, you can send messages to your Paradox session and, with the right code on your form, have those messages trigger any Paradox-related action. This article demonstrates the basic techniques behind this process, shows how to implement it, and provides details about how to do this effectively. Understanding The Basic Process:If you've worked with Paradox for any length of time, you're probably familiar with the menuAction() event, which generally responds to menu commands, though it can be triggered by toolbar buttons, keyboard shortcuts, and even Windows itself. When designing code for menuAction(), you generally compare the value of eventInfo.id() to specific menucommand constants so your code executes only when appropriate. Paradox provides a number of menuCommand constants, each designed for a specific task. For example, menuControlClose fires when the user closes a form using a standard windows control, such as the "X" button in the upper right corner of a form or the Control menu on the upper left corner. Many developers override the default behavior of this menuAction to provide a consistent exit from their forms. You may also be aware that a number of menuCommand constants and eventInfo.id() values have been set aside for custom use designed by the developer. There are really two constants involved: UserMenu, which has a value of 8000 in Paradox v9, and UserMenuMax, which has a value of 9999. Together, these constants outline a range of 2000 unique messages that can be sent to Paradox and used to trigger specific, code-based actions. Note: The specific values of userMenu and userMenuMax can change between different versions of Paradox. For best results, you should check the values of these constants when you're working with a different version of Paradox. You can use msgInfo to quickly determine the values of these constants. For example, consider the following:
msgInfo( "FYI",
"UserMenu = " + string( userMenu ) + "\n" +
"UserMenuMax = " + string( userMenuMax ) )
The numbers between the values of the two constants are ones you can use as custom menu actions. For best results, we suggest using constants in your implementation of these ideas. By combining appropriate menuAction code on a form with an external program that sends messages in this range to Paradox, you can create a communication system between your external program and Paradox. A unique advantage to this process is that it works with any development environment that lets you detect window handles and send messages to specific windows. This means that this technique can be used with Delphi, C++ Builder, Visual Basic, Visual FoxPro, Visual C++, and so on. Even if your development environment doesn't provide direct support, you can add it if your environment lets you link to external DLL's. You can even use this to allow Paradox to control your external application without having to implement features that you may not be completely comfortable with, such as OLE Automation, DDE, or VBA scripting. To implement this process, you need two things:
The first part is pretty straightforward. For example, the following code shows a form's menuAction event:
method menuAction( eventInfo menuEvent )
if not eventInfo.isPrefilter() then ;// fire for the form itself
if eventInfo.id() = userMenu then
msgInfo( "Hello, Developer", "This greeting brought to " +
"you by Paradox via Windows." )
endIf
endIf
endMethod
The steps needed to trigger this greeting depend on your external environment. Again, you need to use one that lets you detect window handles and then send messages to one of those handles. If your environment allows access to the Windows API functions, it's a very straightforward process. For example, here's one way to do this from Borland Delphi:
procedure TForm1.Button1Click( Sender : TObject );
var
h : HWND; // holds Paradox's window handle
begin
h := findWindow( 'PDOXWINDESKTOP', 'Paradox' );
sendMessage( h, WM_COMMAND, 8000, 0 );
end;
end;
If you're not familiar with Delphi, this code:
Please note that the example is simplified for illustration purposes. In production, you will probably want to add error-checking. For example, findWindow() returns a NULL (NIL, in ObjectPascal terms) value if the target window is not currently open. This doesn't necessarily mean that Paradox isn't running, but that findWindow() was not able to find it. While the Windows SDK documentation suggests that findWindow can be used without the window's title, I've personally found it works best if you include both pieces of information. Recent versions of Paradox have used different captions by default and Paradox makes it extremely easy to change the caption. In addition, it's entirely possible that the name of the Paradox Desktop window ("PDOXWINDESKTOP" in version 9) may change between versions. You can determine the name of your Paradox desktop window using enumWindowNames(). Finally, remember that Windows API functions are often case-sensitive. This means you need to search for the exact name of the Desktop window and the title bar caption. A well-designed implementation will take these ideas into account and react accordingly, perhaps by asking the user for permission to launch Paradox or by using a more aggressive approach to finding the Paradox Desktop. You might also want to add code to make Paradox the active application, so that it's not hidden by the external application you're designing. Understanding Windows MessagesWindows' messages support up to two parameter values:
The specific values supported by these parameters depend on the message and application involved. In many cases, WM_COMMAND is a general message that provides specific information in the wParam parameter. WM_COMMAND can indicate the selection of a menu command or the click of certain dialog boxes. It depends on the context of Paradox when it receives the message. When a Paradox form is active, WM_COMMAND tends to suggest menuActions, which is the behavior we're interested in. In this case, the wParam of the WM_COMMAND message tells Paradox that a UserMenu menuAction has occurred, so Paradox fires the event. In turn, this displays the greeting. Additional DetailsWhile it's nice to be able to trigger a single event in Paradox from an external application, that's really only the beginning. For example, suppose you want to build a Delphi application that lists a number of Paradox reports and then triggers code to print the selected report. You can do this by using different userMenu actions to indicate different reports. In implementation, the Delphi trigger code might contain something like this: sendMessage( h, WM_COMMAND, 8000 + rptList.ItemIndex, 0 ) where rptList is the name of a list box containing the names of the available report. Your ObjectPAL menuAction code would then use something along these lines:
siActionID = eventInfo.ID()
switch
case siActionID = userMenu : strRptName = "CUSTOMER"
case siActionID = userMenu + 1 : strRptName = "ORDERS"
case siActionID = userMenu + 2 : strRptName = "INVOICE"
case siActionID = userMenu + 3 : strRptName = "SUMMARY"
endSwitch
rpt.open( strRptName )
If you have a more complicated scenario in mind, you can also use WM_COMMAND's lParam to provide specific details to your menuAction code. For example, suppose your Delphi front-end also lets the user choose from a list of forms. In this case, the trigger code might look like this:
if chxFileType.Checked then
sendMessage( h, WM_COMMAND, 8000, rptList.ItemIndex )
else
sendMessage( h, WM_COMMAND, 8001, frmList.ItemIndex )
endIf
While not necessarily the most elegant design, this shows how a checkbox can be used to select the type of file being opened and also shows how to assign values to the lParam value. In your menuAction code, you might implement this as follows:
siActionID = eventInfo.id()
switch
case siActionID = userMenu : ; run a report
switch
case eventInfo.data() = 0 : strFileName = "CUSTOMER"
case eventInfo.data() = 1 : strFileName = "ORDERS"
case eventInfo.data() = 2 : strFileName = "INVOICE"
case eventInfo.data() = 3 : strFileName = "SUMMARY"
endSwitch
rpt.open( strFileName )
case siActionID = userMenu + 1 : ; run a form
switch
case eventInfo.data() = 0 : strFileName = "NEWORDER"
case eventInfo.data() = 1 : strFileName = "CUSTEDIT"
case eventInfo.data() = 2 : strFileName = "SHIPSTAT"
case eventInfo.data() = 3 : strFileName = "ACCTSTAT"
endSwitch
frm.open( strFileName )
otherwise : doDefault
endSwitch
As you can see, the MenuEvent version of eventInfo provides a data() method that returns the value of the message's lParam. This can provide context to a given userMenu message. Considerations and CreativityBecause Windows' messages only support integer parameters, you might initially think this limits the type of data you can pass from your external program to Paradox. For example, suppose you want to create a Delphi front-end for setting or clearing a date range. (Delphi contains a couple of useful calendar controls for this type of problem.) If you use three different messages, you can create a reasonably useful little utility: For example,
But, there's a problem. How do you tell Paradox the specific date values to use for the first two actions? After all, both Paradox and Delphi store date values as floating point numeric values. Here's where the creativity comes into play. Instead of passing the specific value, how about passing the number of days from a given date, such as the current date? That can easily be expressed in integer values. Since lParam supports long integer values, you have 2,147,483,647 "days" (or ~5,879,328 years) to work with, which should be reasonably adequate for most date values you need to work with in your Paradox applications. As another example, suppose you have one of Corel's clip-art collections and you've built a previewer in Delphi and that you want use this in conjunction with Paradox to build a table of commonly used images. Since Paradox only supports a limited number of image file formats, you have to store the unsupported formats in binary fields. Also, since Paradox converts supported graphic formats to Windows' internal representation, you lose any format specific features, such as compression. So, you choose to store all your graphics in binary fields and to track the extension, so you can use the saved images appropriately. Let's assume your table is called MYIMAGES.DB and contains the following structure: ID I* Description A64 Type A3 Binary M0 Your trigger code would save the image in a file TRANSFER.DAT and send a message to Paradox indicating the format of that image. In your form designed to respond to this message, you might have something like this in the form-level menuAction event:
If not eventInfo.isPrefilter() then
if eventInfo.ID() = UserMenu then
switch
case eventInfo.data() = 0 : strImgType = "BMP"
case eventInfo.data() = 1 : strImgType = "WMF"
case eventInfo.data() = 2 : strImgType = "GIF"
case eventInfo.data() = 3 : strImgType = "PNG"
case eventInfo.data() = 4 : strImgType = "JPG"
; etc.
endSwitch
binVar.loadFromFile( "TRANSFER.DAT" )
liNewID = getNewID( "MYIMAGES.DB", "ID" )
tcImages.insertRecord()
tcImages.update( 1, liNewID, 2, "Mind you, an alternate approach, such as populating the table directly from Delphi, would probably preferable. However, for the sake of the example, you can see how a creative use of the integer limitations can provide you with the solution you're after. SummaryUsing Window's built-in message handling to control Paradox may not seem like the most elegant way to control Paradox from an external application. However, the measure of success for most applications is not the elegance of the solution but the fact that it works. Since message handling is an integral part of Windows itself, you can be reasonably sure that the technique will work in most versions of Paradox and under most variations of Windows. Such a low-tech approach can save you a tremendous amount of time, energy, and frustration while trying to solve an interoperability problem. If you need to coordinate behavior between Paradox and other applications or development environments, this approach may be the most cost-effective, budget-minded solution. |
|||
|
||||||||
|
Copyright © 2000-2004, techtricks.com; All Rights Reserved. Acknowledgements, Disclaimers, Terms and Conditions. |
||||||||
|
Article last updated on 31 May 2003
|
||
|
|
||
|
[- End -]
|