August 1998

Using OWL TDialogs in a VCL application

by Kent Reisdorph

Let's assume for a minute that you have an OWL application you're moving to C++Builder. As part of the conversion process, you'll have to consider converting your dialog boxes to VCL forms. You could argue that VCL forms are easier to use than traditional dialog boxes. But if you're converting a large OWL application to C++Builder, it will be tedious to convert all of the standard dialog boxes that OWL uses to VCL forms. What if you have dozens or even hundreds of dialog boxes? Wouldn't it be nice to know you could use them, and the OWL TDialog class, in your VCL applications? The good news is that you can, as long as you have Borland C++ 5.02. This article will show you how.

A world of troubles

If you don't know the magic combination, you might conclude that it's just not possible to use a TDialog in a VCL application. Your first attempt will probably be rife with compiler errors. If you finally get everything to compile, you'll then be faced with linker errors that seem insurmountable. The main problem is one of namespace pollution. Namespace pollution is a programming buzz phrase that refers to two variables using the same name at the same time but in different contexts. Think about this for a minute. What's the class name for the standard VCL edit component? And what's the class name for the OWL class that represents edit controls? The answer in both cases is TEdit. If both OWL and VCL have a TEdit class, which one is the compiler supposed to use? And this is just one example--both VCL and OWL have classes called TMenu, TBitmap, TListBox, TComboBox, and so on.

In theory, Borland has "fixed" this problem by allowing you to declare the BI_NAMESPACE symbol (BI_NAMESPACE was first implemented in version 5.02 of Borland C++). When this symbol is defined, the OWL classes are wrapped in the namespace called OWL. This means that you should be able to do something like the following:

 

OWL::TEdit* owlEdit;      // an OWL TEdit
Stdctrls::TEdit* vclEdit; // a VCL TEdit
The problem is that in some cases, defining BI_NAMESPACE results in compiler errors in the OWL headers, so you never get this far. Borland's plan was a good one, but somewhere along the way it wasn't completely implemented. So, we have to go back to the drawing board. But rather than dwell on the negative, let's look ahead to what's required to be able to use a TDialog in a VCL application.

Step by step

To use a TDialog in a C++Builder VCL application, you'll need to follow these steps:
  1. Add an RC file containing a dialog box resource to your C++Builder project.
  2. Add the OWL library and include paths to your project's paths.
  3. Create a new source unit that contains the code to call the OWL dialog box.
  4. Write code in your VCL unit to execute the code in the previous step.
  5. Add the proper OWL library files to your C++Builder project.
Let's take a look at these steps individually. Along the way, we'll explain why they're necessary.

 

Add the resource script file to your project

The first step is to have a dialog box created as a resource and contained in a resource script (RC) file. We'll assume that you already have a resource script file or know how to create one. (You can use the Borland C++ 5 IDE to create your dialog box resources, or even the standalone Resource Workshop from earlier versions of Borland C++.) Once you've created the RC file, add it to your C++Builder project using the Add To File option. At build time, C++Builder will see the resource script file in your project and will compile the resources and bind them to the executable file during the link phase.

You'll probably have an RH file as well as the RC file. The RH file is a resource header that contains identifiers for the dialog box resource and for the controls contained on the dialog box. A resource header for a simple dialog box with only two edit controls would look like this:

#define IDD_DIALOG         101
#define IDC_FIRSTNAMEEDIT  102
#define IDC_LASTNAMEEDIT   103
You should include the resource header in any units that reference the resources contained in the RC file. (More on this in just a bit.)

Add the OWL library and include paths to your project

This step is fairly simple. Just open the Project Options dialog box, then enter the path to your OWL INCLUDE directory in the Include Path field and enter the path to your OWL LIB directory in the Library Path field. Be sure to separate the directories with a semicolon. The resulting Include Path field might look like this:
$(BCB)\include;$(BCB)\include\vcl;c:\bc5\
činclude

Create a new unit

Next you need to create a unit that will contain the OWL code. By using a new unit, you'll avoid the namespace issue mentioned earlier--any OWL and VCL classes that have the same name never appear in the same unit. (While there are other ways to avoid the namespace issue, this is by far the easiest.) This new unit (let's call it OWLUNIT.CPP) might contain just one function, which will be called from the main form. The entire unit might look something like this:

 

#include <owl\dialog.h>
#pragma hdrstop

#include "OWLUnit.h"
#include "OWLDlg.rh"

void CallOWLDialog(HWND hWndParent)
{
  TWindow vclWindow(hWndParent);
  TDialog* dlg = 
    new TDialog(&vclWindow, IDD_DIALOG);
  dlg->Execute();
  delete dlg;
} 
Let's take a moment to analyze this code. First, notice that we include the DIALOG.H header file, which is the header for the OWL TDialog class. Notice also that we don't include VCL.H. Since a new unit always adds a line that includes VCL.H, you'll need to delete that line for any units that contain OWL code exclusively. Failure to remove that line will result in the very namespace problems we're trying to avoid.

Next, we include the files OWLUNIT.H and OWLDLG.RH. The header for this unit would probably contain only the function prototype of any public functions in the unit. For example:

#ifndef OWLUnitH
#define OWLUnitH

extern "C"
void CallOWLDialog(HWND hWndParent);

#endif
Note that we use the extern "C" syntax as we would for a function called from a DLL. Doing so is necessary to avoid linker errors later on. The code within the function is as follows:

 

TWindow vclWindow(hWndParent);
TDialog* dlg = 
  new TDialog(&vclWindow, IDD_DIALOG);
dlg->Execute();
delete dlg;
The first line creates an OWL TWindow object from the window handle of the VCL application's main form. This process is known as aliasing a non-OWL window--it's a great OWL feature. We do this because we need a TWindow pointer to call the dialog box, and aliasing the VCL window serves this purpose. Once we have the TWindow alias, we can construct the TDialog object just as we would in an OWL application. In an OWL program, you normally pass this for the parent of the dialog box. Here, we pass the address of the TWindow object we just created. We also pass the resource ID of the dialog box (defined in the RC file when the dialog box was created).

Finally, we call the Execute() method of TDialog to show the modal dialog box. After the dialog box returns, we free the memory associated with it by calling delete. All in all, it's a pretty simple operation.

 

Write code to execute the dialog box from the VCL application

Normally, you'll display a dialog box as the result of a menu item selection or a button click. Assuming you're showing the OWL dialog box as the result of a button click, the code is as follows:
void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
  CallOWLDialog(Handle);
}
That's all there is to it. We need the window handle (HWND) of the form to create an OWL TWindow alias for the VCL form, so we pass the Handle property to the CallOWLDialog() function. At this point, the application will compile. It won't link, however, because the linker doesn't know where to find the definition of the TDialog class and its functions.

 

Add the OWL library files to the project

For the final step, you need to add the appropriate OWL library files to your project. These library files contain the OWL code needed to execute the OWL dialog box. If you forget to add the OWL library files to your application, you'll get linker errors. There are two types of library files: import and static. Most OWL programmers know that you can create an OWL application in one of two ways: with dynamic linking or with static linking. (In VCL applications, there's only one choice: static linking.) If you use dynamic linking, the executable file contains no OWL code itself. Instead, the code is called from the OWL and runtime library DLLs when needed by the application. These DLLs must be shipped with any application that uses dynamic linking. Dynamic linking requires the use of import library files.

When you static link, however, no DLLs are required. Instead, the OWL code needed to run your application is extracted from the OWL and runtime library (RTL) static libraries and is linked into your application at link time.

In theory, you should be able to choose either one of these options for the OWL code in your VCL applications. In reality, there are some problems associated with dynamic linking of the OWL libraries. I won't go into the details, but suffice it to say that the safest route is to choose static linking. While this isn't the final word on the subject, you should note that static linking is the easier and more reliable method at this time.

So how do you choose either dynamic or static linking? By the specific libraries you link into your C++Builder application. For static linking, you need to add the following library files to your project:

 

OWLWF.LIB
BIDSF.LIB
If you were to attempt dynamic linking, then you'd need to add the OWLWFI.LIB and BIDSFI.LIB files. The easiest way to add the library files to your project is with the C++Builder's Add To Project feature. Since you've already modified your project's library path to point to your OWL library path, you can just add the filenames without path information. For example, when you choose Add To Project, a file selection dialog box opens; simply enter OWLWF.LIB in the edit box and then click the Open button. The alternative is to navigate to the \BC5\OWL\LIB directory and find the appropriate library file.

When you use Add To Project to add a library file, C++Builder will add the following lines to your project's source file:

 

USELIB("owlwf.lib");
USELIB("bidsf.lib");
If you prefer, you can manually add these lines to the project source file.

And away we go...

After following the steps we've outlined, you'll have a VCL application that can display an OWL TDialog. ListingsA, B, C, D, E, and F contain a program that displays an OWL dialog box from within a VCL application. The sample dialog box lets you enter a first name and last name. If you click the OK button, then the data entered in the dialog box is displayed in two edit components on the VCL form. Our example goes the extra mile by using OWL's transfer buffer mechanism to transfer data to and from the OWL dialog box. When you look at Listing F (the RC file), you should be aware that we've broken the lines to fit our columns. The lines are broken with the continuation character (a backslash) and will compile as listed although they look a bit odd.

Conclusion

You probably wouldn't set out to write a C++Builder application that uses OWL dialogs. But if you have a legacy OWL app that you want to slowly move to C++Builder, then the ability to display OWL dialogs might be very important to you. Now you know that calling an OWL dialog from a VCL application, although unusual, is possible.

Listing A: OWLDLGU.H

//---------------------------------------------
	#ifndef OWLDlgUH
#define OWLDlgUH
//---------------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
//---------------------------------------------

class TForm1 : public TForm
{
__published:  // IDE-managed Components
  TButton *Button1;
  TLabel *Label3;
  TLabel *Label4;
  TEdit *FirstNameEdit;
  TEdit *LastNameEdit;
  void __fastcall Button1Click(TObject *Sender);
private:	// User declarations
public:    // User declarations
  __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------
extern TForm1 *Form1;
//---------------------------------------------
#endif
Listing B: OWLDLGU.CPP
//---------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop

#include "OWLDlgU.h"
#include "OWLUnit.h"

//---------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::Button1Click(TObject 
  *Sender)
{
  // Create transfer buffer structure, fill it
  // with the contents of the edit components,
  // and pass it to the CallOWLDialog function.
  TransferBuffer buffer;
  strcpy(buffer.FirstName, 
    FirstNameEdit->Text.c_str());
  strcpy(buffer.LastName, 
    LastNameEdit->Text.c_str());
  // Call the OWL dialog.
  CallOWLDialog(Handle, buffer);
  // Transfer the results from the transfer
  // buffer structure to the edit components.
  FirstNameEdit->Text = buffer.FirstName;
  LastNameEdit->Text  = buffer.LastName;
}
Listing C: OWLUNIT.H
#ifndef OWLUnitH
#define OWLUnitH

struct TransferBuffer {
  char FirstName[20];
  char LastName[20];
};

extern "C"
void CallOWLDialog(
  HWND hWndParent, TransferBuffer& tb);

#endif
Listing D: OWLUNIT.CPP
#include <owl\dialog.h>
#include <owl\edit.h>
#pragma hdrstop

#include "OWLUnit.h"
#include "OWLDlg.rh"

void CallOWLDialog(HWND hWndParent, TransferBuffer& tb)
{
  // Create a TWindow alias for the VCL form.
  TWindow vclWindow(hWndParent);
  // Create the TDialog object.
  TDialog* dlg = 
    new TDialog(&vclWindow, IDD_DIALOG);
  // Create the edit controls required for the
  // transfer buffer.
  new TEdit(dlg, IDC_FIRSTNAMEEDIT, 20);
  new TEdit(dlg, IDC_LASTNAMEEDIT, 20);
  // Set the transfer buffer.
  dlg->SetTransferBuffer(&tb);
  // Show the dialog.
  dlg->Execute();
  delete dlg;
}
Listing E: OWLDLG.RH
#define IDD_DIALOG         101
#define IDC_FIRSTNAMEEDIT  102
#define IDC_LASTNAMEEDIT   103
Listing F: OWLDLG.RC
#include "OWLDlg.rh"

IDD_DIALOG DIALOG 0, 0, 240, 120
STYLE DS_MODALFRAME | DS_3DLOOK | \
  DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE | \
  WS_CAPTION | WS_SYSMENU
CAPTION "OWL Dialog in a VCL App"
FONT 8, "MS Sans Serif"
{
 CONTROL "", IDC_FIRSTNAMEEDIT, "edit", \
  ES_LEFT | WS_CHILD | WS_VISIBLE | \
  WS_BORDER | WS_TABSTOP, 60, 44, 100, 12
 CONTROL "", IDC_LASTNAMEEDIT, "edit", \
  ES_LEFT | WS_CHILD | WS_VISIBLE | \
  WS_BORDER | WS_TABSTOP, 60, 72, 100, 12
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON |\
  BS_CENTER | WS_CHILD | WS_VISIBLE |\
  WS_TABSTOP, 186, 6, 50, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", \
  BS_PUSHBUTTON | BS_CENTER | WS_CHILD |\
  WS_VISIBLE | WS_TABSTOP, 186, 26, 50, 14
 CONTROL "This is an OWL Dialog.", 101, \
  "static", SS_LEFT | WS_CHILD | WS_VISIBLE, \
  44, 20, 88, 8
 CONTROL "Frame1", 102, "static",SS_ETCHEDFRAME\
   | WS_CHILD | WS_VISIBLE, 8, 9, 160, 99
 CONTROL "First Name:", 104, "static", \
  SS_LEFT | WS_CHILD | WS_VISIBLE, 20, 47, \
  40, 8
 CONTROL "Last Name:", 105, "static", \
  SS_LEFT | WS_CHILD | WS_VISIBLE, 20, 73, \
  40, 8
}