It's no secret that C++Builder is an incredible programming tool. C++Builder really shines when it comes to designing the user interface for an application. In addition, C++Builder's visual programming capability lets you prototype your application in the shortest possible amount of time, as compared to other C++ programming environments.
But C++Builder is more than just another pretty face--you can use it to write console applications and Dynamic Link Libraries (DLLs), too. Even though a console application is character-based (non-graphical), you can still call common dialog boxes or VCL forms from the application if you need to. In this article, we'll explain how. (If you need a refresher on console applications, see "What's a Console Application?")
Okay, that was easy enough. Now you need to create a form that you can display from the console app. To do so, click the New Form button on the C++Builder ToolBar, or choose File | New Form from the main menu. Obviously, we haven't yet written any code to display the form, but you might be surprised to learn that the application won't even compile right now. The console application has no knowledge of VCL at this point, so you'll have to educate it. To better understand this process, let's talk a little about how C++Builder operates when you're building a GUI application.
When you add a form to a Windows GUI application, C++Builder places the following line in the project source file:
USEFORM("Unit1.cpp", Form1);
USEFORM is a VCL macro--it alerts VCL that you're using a form and provides the name of the form's source unit. This process allows C++Builder to auto-create forms and to set up some of the other housekeeping chores that VCL does in the background on your behalf. It isn't black magic, but it is VCL's way of hiding details that you don't need to worry about (after all, that's part of the beauty of RAD development).
When you add a form to a console application, C++Builder adds the same line to the application's source code. The problem is that your console application--since it isn't a VCL application--doesn't know what the USEFORM macro is. So, you need to include the VCL.H header. To do that, add the following line below the other includes and just above the #pragma hdrstop line:
#include <vcl\vcl.h>Now the console application will compile, because the compiler can see the USEFORM macro declaration as well as the rest of the VCL declarations.
Next, you need to be sure that the console application can find the declaration for the form's class. When you add a new form to a GUI application, you make the form available to any units that reference the form by choosing File | Include Unit Hdr... from the C++Builder main menu. For some inexplicable reason, this menu item is unavailable when you're building a console application. As a result, at the time you add a form to a console application, you must manually enter the code to include the form's header. To do so, add a line like the following to the project's main unit, this time below the #pragma hdrstop line:
#include "Unit1.h";
This line ensures that the compiler will be able to find the class declaration when you reference the form in your console application's code.
In a GUI application, C++Builder auto-creates forms unless you specify that it shouldn't. If a form is auto-created, then you can show it without any fuss, as follows:
Form1->ShowModal();C++Builder creates a pointer to every form it adds to a project via the New Form menu item. The pointer's variable name is the same as the form's Name property. In the previous example, the pointer name is Form1, and the pointer is type TForm1. The parent object automatically deletes the pointer when the parent itself is deleted. The process is essentially automatic in a C++Builder GUI application--you don't have to spend any time thinking about it.
But, in real-world applications, not all forms are auto-created. If a form isn't auto-created, you need to specifically create the form before you can show it. In that case, the code looks like the following:
Form1 = new TForm1(this); Form1->ShowModal();A console applicationdoesn't allow auto-creation of forms, so you must explicitly create the form object before you try to use the form's pointer. In addition, you must explicitly delete the pointer when you're done with it. In this case, we recommend that you use a local variable instead of the global variable created by C++Builder, as follows:
TForm1* form = new TForm1(0); form->ShowModal(); delete form;While you're not required to use a local variable, you'll probably find that doing so better documents what's happening in your code. The previous code segment creates the TForm1 object, shows the form, and then destroys the TForm1 object when the form is dismissed. In this situation, you take the responsibility for creating and deleting the object.
Notice that this code specifies 0 as the form's parent, rather than this. We'll discuss the reason next.
Typically, when you create a form at runtime in a GUI application, you'll use code like the following:
TForm1* form = new TForm1(this);In a GUI application, the this pointer is some descendant of TComponent and everything works fine. In a console mode application this (no pun intended) approach won't work because the this pointer is only valid within a class. Or, to put it another way, there is no this pointer within the main() function of a console application.
So what should you do? You have two basic choices. One choice is to supply no owner for the form. In other words, you can just pass a 0 in a form's constructor, as follows:
TForm1* form = new TForm(0);If you do so, you have the responsibility of deleting the form at some point before the application terminates. If you fail to delete the pointer, then the application will leak memory.
Your second choice is to supply the Application object as the form's parent. (Even though you've written a console application, VCL still creates an Application object--it does so automatically whenever you use VCL objects in a console application.) If you choose this route, you'll write code something like this:
TForm1* form = new TForm(Application);The advantage here is that you don't have to delete the form specifically, because the Application object is the owner of the form--it will delete the form when the application closes. It doesn't really matter which method you use, but we prefer to use 0 as the owner and take the responsibility of deleting the form as soon as we're done with it.
As you probably know, once you display a modal window, the user must close the window before he or she can do anything more with the application. In effect, the parent window is disabled while the modal window is being displayed.
In the wonderful world of the Windows API, displaying a modal dialog box is fairly straightforward because Windows takes care of disabling the parent window for you. You don't have to do anything but display the modal dialog box (although it takes about 50 lines of code to get to that point!). To the VCL programmer, displaying a form modally is even easier--all you need to do is call the ShowModal() method.
However, what goes on behind the scenes in a VCL application is a little more complicated. Because a form isn't a true dialog box, VCL must take responsibility for disabling the parent window and then re-enabling the parent window every time ShowModal() is called. While this process isn't exactly rocket science, it's one of the little things VCL does for you that you probably take for granted.
The point is that when you call a form from a console application, you can't parent the form to the console window, and no automatic enabling and disabling of the parent window takes place. Therefore, it's up to you to disable the console window prior to calling ShowModal(), and it's also up to you to re-enable the console window after ShowModal() returns.
First, you must determine the window handle of the console window. (For more information on finding the window handle for a console application, see "Finding a Console Window.") Once you know the window handle, the code to simulate a modal dialog box in a console application looks like the following:
EnableWindow(hWnd, false); TForm1* form = new TForm1(0); form->ShowModal(); delete form; EnableWindow(hWnd, true);This code will allow the console application to behave somewhat like a GUI application. That is, it will be disabled as long as the form is being displayed.
Note that there's at least one problem associated with this technique: Users won't be able to use [Alt][Tab] to tab to the console application while it's disabled. Well, nothing's perfect.
Another issue you'll contend with when simulating a modal dialog box is window focus. Since the form technically doesn't belong to the console window, the console window won't automatically have focus when the form closes. A call to the API function BringWindowToTop() after the form closes will remedy that situation.
ShowWindow(Application->Handle, SW_HIDE);Listing A contains a program that illustrates the use of a form and the use of the Open File dialog box in a console application. When the application runs, you can choose menu selection 1 to display the Open File dialog box or menu selection 2 to display a form. To try this program, create a new console application, add a form of any flavor you like, and then enter the code.
Listing A: ConsForm.cpp
//-------------------------------------------------- #include <vcl\condefs.h> #include <stdio.h> #include <stdlib.h>
// Add these includes: #include <vcl\vcl.h> #include <conio.h> #include <iostream.h> #pragma hdrstop
// Include STL string header. #include <string>
// Include the form's header. #include "Form.h"
// Declaration for the GetHWND() function.
HWND GetHWND();
//--------------------------------------------------
USERES("ConsForm.res");
USEFORM("Form.cpp", Form1);
//--------------------------------------------------
int main(int argc, char **argv)
{
// Get the window handle of the console window.
HWND hWnd = GetHWND();
// This is just so that the console window has
// focus when run from the debugger.
BringWindowToTop(hWnd);
// A little menu loop.
bool done = false;
do {
clrscr();
cout << endl;
cout << "Please choose one of the following.";
cout << endl << endl;
cout << "0. Quit" << endl;;
cout << "1. Simulate File Open" << endl;
cout << "2. Show Form" << endl;
int choice;
do {
choice = getch();
choice -= 48;
} while (choice < -1 || choice > 2); // Do something based on the choice made.
switch (choice) {
// All done. Get out of loop.
case 0 : {
done = true;
break;
}
// Show the common file open dialog.
case 1 : {
// Disable the console window.
EnableWindow(hWnd, false);
// Create an instance of the TOpenDialog
// class and set the filter.
TOpenDialog* dlg = new TOpenDialog(0);
dlg->Filter = "All files (*.*)|*.*";
// Show the dialog.
bool result = dlg->Execute();
// Enable the console window and bring it to the top.
EnableWindow(hWnd, true);
BringWindowToTop(hWnd);
// If the OK button was pressed then
// show the name of the file chosen.
if (result) {
cout << endl << "The file opened was: " << dlg->FileName << endl << endl;
cout << "Press any key to continue...";
getch();
}
// Delete the dialog object.
delete dlg;
break;
}
case 2 : {
// Disable the console window.
EnableWindow(hWnd, false);
// Create an instance of the TForm
// class and show the form.
TForm1* Form1 = new TForm1(0);
int x = Form1->ShowModal();
// Enable the console window and
// bring it to the top.
EnableWindow(hWnd, true);
BringWindowToTop(hWnd);
// Display the results of the modal form.
cout << endl << "Modal result: " << x << endl;
cout << endl << "Press any key to continue...";
getch();
delete Form1;
break;
}
}
} while(!done);
return 0;
}
//------------------------------------------------------------
// This routine gets the window handle of the console
// window. Win95 and NT handle console windows
// differently so we have to check and see what OS
// we're running under and take appropriate measures.
HWND GetHWND()
{
char title[256] = "";
char className[20] = "";
// Get the name of this executable.
GetModuleFileName(0, title, sizeof(title));
// See if we're running on Win95 or NT.
TOSVersionInfo info;
info.dwOSVersionInfoSize = sizeof(info);
GetVersionEx(&info);
int platform = info.dwPlatformId;
// If NT then the class name is 'ConsoleWindowClass'
// and the title is the entire path and filename.
if (platform == VER_PLATFORM_WIN32_NT)
strcpy(className, "ConsoleWindowClass");
// If Win95 then the class name is 'tty' and
// the window title is just the filename
// without path and extension.
else {
strcpy(className, "tty");
// Strip off everything but the file name.
std::string name = title;
int pos = name.find_last_of("\\");
name.remove(0, ++pos);
name.remove(name.length() - 4, 4);
strcpy(title, name.c_str());
}
// Return the result of FindWindow().
return FindWindow(className, title);
}
Kent Reisdorph is a editor of the C++Builder Developer's Journal
as well as director of systems and services at TurboPower Software Company, and
a member of TeamB, Borland's volunteer online support group. He's the author of Teach
Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days.
You can contact Kent at editor@bridgespublishing.com.