Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Visual Envy


Dr. Dobb's Sourcebook May/June 1997: Visual Envy

Al is the author of Developing ActiveX Web Controls and the forthcoming Developing ActiveX Controls with Visual Basic 5 (both from The Coriolis Group). You can reach Al on the Web at http://www.al-williams.com/awc/.


C++ programmers are an elite bunch. Well, some people would say we are snobs. I'm not sure exactly why, but most C++ programmers disdain non-C++ tools like Visual Basic, Delphi, and PowerBuilder. Despite this disdain, these tools allow you to rapidly develop certain types of applications. While Visual C++ has the word "visual" in the title, it isn't nearly as visual as other tools, say, Visual Basic. Visual C++'s tools require you to have detailed knowledge about MFC application architecture, message routing, and other arcane issues.

Does this mean that C++ programmers can't use the same sort of productivity tools available to visual programmers? Not anymore. Borland has revamped its respected C++ compiler to support a complete visual environment based on its Delphi environment. Like Delphi, you can visually place components to create interfaces, connect to databases, and manage OLE. Unlike Delphi, Borland's C++Builder uses C++ for all user programming.

In this article, I'll show you how to get started with C++Builder. Along the way, I'll show you how C++Builder manages databases with visual components. You can often write complete database programs without writing a single line of code. And if you do add code, it will be C++ -- not Pascal or PowerScript.

Included with the C++Builder client/ server suite is Borland's Interbase database, a product similar in scope to Oracle or Sybase. You can use SQL to manipulate data or apply several visual tools. I decided to use Interbase as the database engine. However, since C++Builder hides many details about the underlying database, you won't really care much. The code would easily work with dBase, Paradox, or any other ODBC- or IDAPI-compliant database.

A Quick Tour

As Figure 1 illustrates, C++Builder looks a lot like Delphi. You start with a form (just a regular window, really) and a component palette near the top. You use the mouse to place components from the palette on the form. When you select a component (or a form, since a form is just a component), you can view its properties in the property inspector window. Each component has its own properties that control its appearance and behavior. For example, a button's properties include Height, Width, Caption, and Name. You can also change most of these properties at run time using C++ code if you like. There are many properties you can only access at run time.

C++Builder associates each form in your program with a C++ class. When you drop a component on the form, it creates a corresponding variable in the form class. This is how you set properties and call methods for the component.

Each component generates events. These events usually correspond loosely to a Windows message. You can view a list of events in the property inspector window (see Figure 2). If you type a name in one of the slots in the inspector, C++Builder will generate a function to handle the event. You can also double-click most components to access their most common event.

Suppose you double-click a button. You'll see a code window (Figure 3) that contains a skeletal C++ member function. Notice that the function is a member of the form class that contains the button. If you wanted to show Form2 (another form) as a modal dialog, you might write: Form2->ShowModal();.

Of course, you can run your program and debug it from inside the C++Builder environment. You can call the Windows API (or other DLLs), too. You can do all the things you expect to be able to do in C++ -- work with templates, exception handling, callback functions -- whatever you want.

A Project

Since many visual development tools focus on client/server database programs, I wanted to do something similar with C++Builder. I decided to write a program to catalog removable disk volumes. This would allow a company to keep a library of floppies, Zip disks, or removable hard drives. The program allows the company librarian to maintain a database of files accessible from a central server.

I started with a simple design using two tables: Volumes and Files. The Volumes table (see Table 1) contains an entry for each removable disk in the library. It associates each volume with a unique ID (the VID). This VID is a foreign key for the Files table (Table 2). The Files table stores filenames, path information, and an optional description. Each record has a VID field that associates it with a particular volume.

User Interface

Designing the user interface couldn't be simpler. Just drop the controls you want on the form. If you want to make another form, just insert it using the New Form item on the File menu. Dialog boxes are really just forms with certain properties (see Table 1). If you don't want the form to show up right away, design it so that its Visible property is False. Then, call the Show method later to bring the form into view. There's no difference between a form and a modeless dialog. If you want the form to act like a modal dialog, call ShowModal instead of Show. Then, when you want to close the dialog, set the ModalResult property to a nonzero number. You can also set a button's ModalResult property to a nonzero value to cause it to close its parent dialog, if you like.

Most of the controls for this project are data-aware controls. You'll see later that these controls will automatically interface with your database tables. Most of these controls are variants of ordinary controls. For example, a data-aware edit control is similar to an ordinary edit control except for its database capabilities.

However, one interface component is different: the database grid. Look at the bottom of Figure 4. Notice the spreadsheet-like grid? That's a database grid (or DBGrid). It displays a database table (or a subset of the table) in a tabular format.

Notice the navigation buttons on the form in Figure 4. You could construct them from individual buttons. However, C++Builder provides a special DBNavigator component just for moving through databases.

Database Access with Tables

C++Builder provides multiple ways to access databases. Your preference will depend on what you want to accomplish. Usually, you'll only use one way, but in the example program, I used several different ways so you can see how everything works.

C++Builder provides several components that work together to access your database (see Figure 5). Many of these components are not visible at run time -- they are only useful to the programmer.

At the root of all database programs is the Database component. This component forms a physical link to the database of your choice. You can either specify the database directly, or you can use an alias. Using aliases allows you to change the location of a database without modifying anything in your program. Of course, that implies that end users can change aliases (which might not be true). Either way, you use a TDatabase object. Your database might contain one table or one hundred tables -- it's not important. The TDatabase component simply links your program to the appropriate database. If you use an alias and a local database, there are some cases where you might not need a TDatabase component. Still, you can always use such a component, and you'll need to use one for client/server programs, regardless.

The next object you'll need is a TTable component. Actually, you need one for each table you want to work with. One of the properties for TTable is the name of the database object you want to use. If you use an alias to a local database, you can simply specify the alias in the TTable object's properties. Usually, you'll use the name of a TDatabase, however. Another property of TTable sets the name of the specific table you want to use.

If you just want to look things up in tables and retrieve their values, you don't need to go any further than using TTable. The TTable component has Field and FieldValue properties that allow you to fetch (or set) data. For example, if your component is Tbl1 and you want to read the current value of the VID field as an integer, you might write: x=Tbl1.FieldValue("VID").AsInteger();.

You can also call the table's SetKey method to alter the meaning of the fields. When the table is in SetKey mode, data you write to the fields become a search key. C++Builder will use the search key during the next search you perform.

Usually, you won't directly manipulate the fields very much. Instead, you want to have the C++Builder data-aware controls do it for you. Then, you need a TDataSource component for each table you are using. You set the data source's properties to refer to a TTable object, and then you can place data-aware components on your form. Each data-aware component has a property to set the name of the data source it uses. It also takes the name of the field you want it to use.

Once you have these components in place (they are all on the component palette), they just work. You don't have to write any code at all. If you set the Active property to True in the TTable component, C++Builder will even display the data live during design time. If you don't set the component to Active, it will still display data at run time. If you drop a TDatabase, TTable, TDataSource, and TDBGrid component on a form, you'll be able to see your entire database without writing any code. If you prefer, you can use TDBEdit components (one for each field) and add a TDBNavigator component. Then you can scan your records one at a time without any programming at all.

Master Detail Databases

The TTable component allows you to enslave your table to another TTable. This lets you easily implement master/detail databases. For the example program, the master table is the Volumes table. The detail table is Files. The Files table's MasterSource property is set to the data source for the Volumes table. Also, the MasterFields property contains the join field (VID in this case; you can specify multiple fields). C++Builder automatically takes care of synchronizing the tables. When you move to a new master record, the detail records change automatically.

Database Access with SQL

If you are a seasoned database professional, you'll probably prefer to use SQL to access and manipulate your database. That's not a problem. Simply use a TQuery component in place of the TTable object. If your SQL generates rows (like Select, for example), use the Open method to execute your statements and provide the rows to the associated TDataSource component. If your SQL doesn't generate rows, you can use ExecSQL instead. For simple programs, you'll usually write a query at design time and set it in the TQuery's properties. However, you can change the SQL at run time, if you like.

Data Form Wizard

The Form Expert will automatically create forms with all the components you need to display database records. Even though this wizard-like utility will write the code for you, it is still important to understand what it does (since you will undoubtedly want to change the code to suit your needs).

The first screen of the Expert allows you to set several options (see Figure 6). You can decide if you want one data source or a master/detail database form. You can also tell the Expert if you want to use TTable or TQuery objects to select data.

On the next screen, you select the database you want to use (see Figure 7). One problem is that the Expert only seems to understand dBase and Paradox files. Of course, you can always make dummy databases with Borland's Database Desktop and use them to design the form. Figure 8 shows the screen you'll use to place fields from the database into the form. You can pick any fields you want. Finally, you specify how you'd like the form laid out (as shown in Figure 9) and decide if you want the form to be the main form (as shown in Figure 10).

The Expert offers to generate a data module. This is just a separate class that contains the tables and data source. Usually, you won't select this option and the Expert will put the table and the data source directly on the form.

The Example

You can find the code for the example database application in Listings One and Two The main form (Listing One) uses the simple table-based scheme to browse the database. This takes no code, just components. (The complete source code and related files are available electronically; see "Programmer's Services," page 3.)

The DBNavigator control has a delete button (as well as insertion buttons) by default. However, deleting a master record doesn't delete the detail records. You can arrange for that by handling events from the table, but I decided to just make my own delete button and put the deletion code there.

I also added code that inserts data by reading a volume from a disk drive. This code uses SQL (via a TQuery) and makes extensive use of the Windows API. There are several subforms (modal dialogs) involved in this command that are not very interesting. You can find the code for them online.

When you click on the Find button, the program makes the Find form visible (see Figure 11 and Figure 2). Notice that the form is modeless, so you can switch back and forth between the forms if you like. Initially, the Find form has an ordinary edit control and a disabled button on it. When you type into the box, the button enables. Pressing the button causes the program to search for the filename and place the results in a previously invisible DBGrid. I used a TQuery to perform this search, which involves joining both tables.

Conclusion

It could hardly be easier to access a database using C++Builder. And when the going gets tough, C++Builder can still do what you want. For example, recursively scanning the disks and learning the disk volume name and serial number are trivial Windows API functions in C++Builder -- not all visual development tools can easily access these functions.

Don't forget, C++Builder isn't specifically a database tool; it can do many other things as well. Anything you'd normally write in C++, you could write in C++Builder. Why let the VB guys have all the fun?

DDJ

Listing One

//---------------------------#include <vcl\vcl.h>
#pragma hdrstop


</p>
#include "vdb.h"
#include "dirselsrc.h"
#include "voldlg.h"
#include "ff.h"
//---------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------
void __fastcall TForm1::DelCmdClick(TObject *Sender)
{
  Variant volid=VID->Text;
  Set<Db::TLocateOption,0,1> opts;
  if (MessageBox(Handle,"Delete entire record",
    "Confirm",MB_YESNO|MB_ICONSTOP)== IDNO) return;
// Delete all
  while (FileTbl->Locate("VID",volid,opts))
    FileTbl->Delete();
// delete main
  VolTbl->Delete();
}
//-------------------------
void __fastcall TForm1::ScanCmdClick(TObject *Sender)
{
  char drive[4];
  char volname[33];
  DWORD serno;
  int newvid;
  // Prompt for volume to read
  DirSel->ShowModal();
  if (DirSel->ModalResult==mrCancel) return;
  drive[0]=DirSel->DirBox->Drive;
  drive[1]=':';
  drive[2]='\\';
  drive[3]='\0';
// Find volume label
  GetVolumeInformation(drive,volname,sizeof(volname),
     &serno,NULL,NULL,NULL,0);
// Prompt for volume name (default=label or serial #)
  if (!*volname)
    sprintf(volname,"SN:%x",serno);
  VolForm->VolName->Text=volname;
  VolForm->ShowModal();
  if (VolForm->ModalResult==mrCancel) return;


</p>
// Insert and generate new VID
  char workstr[256];
  VolQuery->SQL->Clear();
  sprintf(workstr,
    "insert into volumes (VID,VOLUME) "
    "VALUES(GEN_ID(VIDGEN,1),\"%s\")",
    VolForm->VolName->Text.c_str());
  VolQuery->SQL->Add(workstr);
  try
    {
    VolQuery->ExecSQL();
    }
  catch (EDBEngineError &e)
    {
    int n=e.Errors[0]->ErrorCode;
    if (n==9729)  // duplicate key?
     {
     MessageBox(Handle,"You must pick a unique volume name",
       NULL,MB_OK|MB_ICONHAND);
     VolQuery->Close();
     return;
     }
   throw; // what was that?
   }
  VolQuery->Close();
  VolQuery->SQL->Clear();
  VolQuery->SQL->Add("select max(vid)"
    "result from volumes");
  VolQuery->Open();
  newvid=VolQuery->Fields[0]->AsInteger;
  VolQuery->Close();
// Scan files recursively and add to FILES table
  ScanPath(newvid,drive);
// done so refresh!
  VolTbl->Refresh();
// and Seek new volume so it shows up
  VolTbl->IndexFieldNames="VID";
  VolTbl->SetKey();
  VolTbl->FieldByName("VID")->AsInteger=newvid;
  VolTbl->GotoKey();
}
//-------------------------
// This non UI function scans a disk recursively
void __fastcall TForm1::ScanPath(int vid,char *path)
  {
  WIN32_FIND_DATA fd;
  HANDLE h;
  SetCurrentDir(path);
  if (!(h=FindFirstFile("*.*",&fd))) return;
  do {
// recurse directories
     if (fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
       {
       char down[MAX_PATH];
       if (!strcmp(fd.cFileName,".")||
           !strcmp(fd.cFileName,".."))
         continue; // don't do . or .. directories
       strcpy(down,path);
// add backslash if not present
       if (down[strlen(down)-1]!='\\') strcat(down,"\\");
       strcat(down,fd.cFileName);
       ScanPath(vid,down);     // do it again
       SetCurrentDir(path);    // restore old path
       }
     else
       {  // insert file
       char ins[MAX_PATH*2];
       InsertQ->SQL->Clear();
       InsertQ->SQL->Add("insert into files "
         "(vid,filename,path) values (");
       sprintf(ins,"%d,\"%s\",\"%s\")",vid,
         fd.cFileName,path+2);
       InsertQ->SQL->Add(ins);
       InsertQ->ExecSQL();
       InsertQ->Close();
       }
     } while (FindNextFile(h,&fd));
  FindClose(h);
  }
// do find command
void __fastcall TForm1::FindFileCmdClick(TObject *Sender)
{
  FFForm->Show();  // just show it
}
//-------------------------


</p>


</p>

Back to Article

Listing Two

//---------------------------#include <vcl\vcl.h>
#pragma hdrstop


</p>
#include "ff.h"
//---------------------------
#pragma resource "*.dfm"
TFFForm *FFForm;
//---------------------------
__fastcall TFFForm::TFFForm(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------
// Execute here when searching
void __fastcall TFFForm::SearchBtnClick(TObject *Sender)
{
  char q[MAX_PATH*2];
  sprintf(q,
    "select Volume, path, descrip from volumes, "
    "files where files.filename= '%s' "
    "and volumes.vid=files.vid",
    StrUpper(FileName->Text.c_str()));
  Query1->Close();
  Query1->SQL->Clear();
  Query1->SQL->Add(q);
  Query1->Open();
  DBGrid1->Visible=TRUE; // Show DBGrid
}
//-------------------------
// If name entered in file name enable search
void __fastcall TFFForm::FileNameChange(TObject *Sender)
{
  SearchBtn->Enabled=TRUE;
}
//-------------------------

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.