Examining Borland Delphi 2.0

Borland's recently released Delphi 2.0 environment supports 32-bit applications, client/server development, team programming, and more.


June 01, 1996
URL:http://www.drdobbs.com/web-development/examining-borland-delphi-20/184409901

Figure 2

Figure 3

JUN96: Examining Borland Delphi 2.0

Examining Borland Delphi 2.0

Support for client/server databases, multithreaded apps, and more

Ted Faison

Ted is president of Faison Computing, a firm that develops Delphi, C++ applications, class libraries, and software components for Windows. Ted can be reached at [email protected].


When compared to Version 1.0, Borland's recently released Delphi 2.0 is a big step forward. Delphi 2.0 includes enhanced support for OLE automation, MAPI applications, and Windows 95 controls. One of the biggest differences, however, is that Delphi 2.0 generates 32-bit applications, delivering a substantial performance increase over 16-bit Delphi apps. But because Delphi 2.0 is a 32-bit environment, it doesn't run under Windows 3.1, only Windows 95 and Windows NT. (Borland says it will continue to support 16-bit Delphi 1.0 until sometime next year.) Delphi 2.0 comes in three configurations:

The Client/Server Suite, which I'll focus on in this article, also comes integrated with the PVCS version-control system, allowing team programmers to work on the same files without undoing each other's work. Other additions to Delphi include C-like double slashes to begin a single-line comment, the Visual Component Library (a collection of 100 drag-and-drop tree views, sliders, progress bars, rich-text editing, list views, and status bar controls). The Client/Server Suite includes source code for the library.

The Integrated Development Environment

Application development programs are expected to integrate the various phases of code editing, compiling, and linking. The Delphi IDE (see Figure 1) does this, and goes one step further. Because Delphi supports user-interface design with parallel code generation, it also has a Forms Designer that works in tandem with the Code Editor window. There is a button on the toolbar that lets you flip back and forth between a form's graphical view and its source code. If you add controls to a form, the code window is updated immediately and automatically.

Delphi projects usually entail multiple forms and code units, and Delphi makes it easy to manage the code units by keeping them all on a single tabbed window. Changing a unit is a one-click operation, and all the names of all open units are always visible on the tab bar. Most other programs employ the older MDI approach, where you switch between files using the Window menu.

The Delphi IDE is good but not perfect. With so many overlapping, independent windows sharing the screen--an Object Inspector, Code Editor, Forms Designer, main toolbar, and so on--the IDE can be a nuisance. I find it much easier to manage a single main window subdivided into moveable subpanes, as in Microsoft Visual C++ or even Borland C++ ClassExpert. The Object Inspector is nice for small projects, but inconvenient to use to get around in a larger project. (What might be nice is some kind of object-navigation outline pane, where you could see a complete hierarchy of all the objects in a project.)

The tabbed toolbar is nice, given the number of tools provided by Delphi. Because you can install your own tools, it is possible to run out of space on the toolbar, so Borland provided a set of scroll bars. You can also create new tabs on the tool bar, so you can organize your tools any way you want.

The integrated debugger also is easy to use. The tool bar has buttons to start, pause, step into, and step over code. A step out button is not available. You would use that button when you're single stepping inside a function and want to return to the calling function. Most IDEs have a step out command, including Borland C++. Why the Delphi team left it out is a mystery.

Something else I miss is an interactive tooltip for object inspecting at debug time. When you put a breakpoint in your code, you often want to inspect the value of a variable. Visual C++ pioneered the concept of tooltip inspection, where you move the mouse over a variable. If the variable is scalar and in scope, a tooltip window automatically appears, showing the variable's value. In Delphi, you have to right-click the variable, then chose the Evaluate/Modify command from the popup menu.

Delphi Reusability

Delphi was designed to make components reusable. For example, assume you create a form that has special tools to control image effects when manipulating TIFF images. In Delphi, you can place the form in a special Component Repository (stored components organized into pages), making it available for subsequent reuse. To do so, first you create the form, then use the Project | Add to Repository command. You select a page for your form, a name, description, and author; then the form is stored. The repository is displayed in response to a File | New... command, appearing as a tabbed dialog box.

You can add your own pages to the repository, effectively turning it into a software-component library. Once a component is in the repository, it can be reused in three ways:

Almost anything can be reused through the repository. The only essential requirement is that the object be a Visual Component Library (VCL) component, meaning that it must be derived from the Delphi VCL class TComponent. You can even reuse an entire application--Delphi's way of creating new projects--rather than using some kind of Project Expert or Wizard.

There is no limit to the complexity of forms in the Delphi repository. For instance, say that you need a form like the About box in the repository, but you need to add a Help button to it. In C++ you're out of luck--you can't inherit and make changes to dialog templates. You have to create a new template by copying the original form, being careful to match all the child IDs of the reused controls, so you can reuse the C++ code attached to the dialog.

It's much simpler in Delphi. You derive a form from the About Box form, and simply add the Help button graphically to it. The resulting derived form inherits the contents of the base form. Changing the base class form affects all forms derived from it, just like with ordinary classes. It gets even better. When I added the Help button to my derived form, I adjusted the position of the inherited OK button, so the buttons would be centered in the form. Delphi inherited the original OK button and overrode its position.

Built-in Multithreading Support

Many programmers shy away from threads because of the complexities of thread synchronization and maintenance. Delphi makes thread programming simple for many applications, hiding the details of thread start-up, synchronization, and termination.

Threads often are the solution to user-interface problems. Say you need to perform some lengthy operations, such as obtaining records from a database or printing a file. If you build the code straight into the app, users have to wait until the operation terminates to continue with the program. If you want to let users abort the operation by clicking a Cancel button, you need to add a loop that periodically checks the button. With multithreaded code, you create a worker thread to handle the lengthy operation, and you monitor the Cancel button in the main thread. If the user hits the Cancel button, the main thread terminates the worker thread.

For example, assume you have a form called TMyForm with a Start button to kick off some operation and an Abort button to terminate it. The operation itself will be performed by a class derived from the VCL class TThread, declared in Listing One. The Execute method, where your code gets invoked, overrides the virtual function by the same name in the base class. To use the new thread, you add an instance of it to the form or class that will control the thread; see Listing Two. To start the lengthy operation, you add a handler for the start button; see Listing Three. To abort the operation, you just call the thread's Terminate method in the handler for the form's Abort button; see Listing Four.

Programming at a High Level

Delphi uses VCL components to completely encapsulate the Windows API, providing a high-level programming interface. You might say that Delphi is to C++ what C++ is to C. The result is that you can get the job done much easier and with less code. Consider the process of drawing something on the screen. All Delphi components that are displayable inherit a method called Paint. You override this method and use a special Delphi Canvas object to draw on. Canvas is a high-level abstraction of a Windows device context. It handles all the drudgery of creating, selecting, deselecting, and destroying GDI objects for you. To draw a circle, for instance, you add a Paint method to your form or component and draw on the Canvas; see Listing Five.

You can change the attributes of the GDI objects, using simple assignment statements on their properties. To create a navy-blue pen, you use the statement Pen.Color := clNavy;. You don't even have to create the pen, because Delphi does it for you automatically. Because Color is a property of TPen, changing its value invokes an accessor function that takes care of perfunctory Windows details automatically.

Even though Delphi encapsulates the Windows API, you can still get under the hood to handle low-level or unsupported Windows features. If you want to hook the message dispatcher, a simple WndProc handler will take care of it. Say you want to intercept mouse clicks and typed characters for a form. The WndProc might look something like Listing Six. Calling the inherited method lets the base class Delphi handler process other events in the default way. Using a WndProc is considered a low-level message-handling scheme. For all Windows and internal Delphi messages, the proper way to install a message handler is through the message keyword. To add a handler for the WM_LBUTTONDOWN message, you add a class method like procedure WMLButtonDblClk(var Message: TWMLButtonDown); message WM_LBUTTONDBLCLK;.

Before adding a message handler to a class, make sure Delphi hasn't already provided an event-handler hook. For most commonly used messages and events, Delphi provides easy entry points, using the Events page of the Object Inspector. The Events page is usually the best way to add message handlers to a VCL component. To provide a handler that is invoked when your button is clicked, enter a handler name in the OnClick field. The handler can have any name you want. By double clicking the empty field, Delphi will create a default name, based on the event type and name of the control. Delphi creates an empty handler with correctly formatted arguments in the parameter list. Listing Seven is the default handler for the OnClick event. All event handlers are passed a pointer to the object that sent the message.

Database Support

For database programming, Delphi includes a complete set of VCL components to handle everything from simple table viewers to multiple-join queries to triggers and stored procedures. The heart of the architecture is the Borland Database Engine (BDE), which serves a role similar to the Microsoft ODBC Driver Manager. When developing database applications, you typically populate forms with a series of database-aware controls, like editboxes, listboxes, and grids. The TTable and TQuery components encapsulate the details of connecting with a database. A TDataSource component manages the mapping of database fields to data-aware components. Using a VCL navigator tool (which appears as a set of VCR buttons), you can move the cursor in the result set. Figure 2 shows the relationship between an application and database components.

All database access is through the BDE, including access to ODBC data sources. Compared to direct ODBC access, accessing ODBC data sources incurs a slight performance hit because of the pass-through of commands from the BDE DLL to the ODBC Driver Manager DLL. For operations that produce multiple records, the overhead is negligible. If your app makes lots of single-record operations, some degradation may be perceptible.

Access to all client/server SQL databases is through SQL Links, a Borland DLL that uses vendor-specific drivers to talk to the underlying databases. I tested Delphi with Oracle 7.2, the current version, using Personal Oracle. (InterBase is Borland's SQL database. It is a full 32-bit client/server database that competes against Oracle and Sybase SQL Server.) After getting Oracle running with Delphi, everything worked, although a real application will need to provide custom handlers for the multitude of database exceptions that may originate (otherwise Delphi will display opaque message boxes). Accessing Oracle data was just a matter of adding a TTable or TQuery component, sprinkling in a TDataSource, and putting database controls on a form. Oracle log-on was supported automatically. Delphi 2.0 is configured to work with Oracle 7.1, but I had to use the BDE Configuration Utility to change a number of low-level settings to get the BDE to work with Oracle 7.2.

While the Delphi side was easy, it took time to get the BDE to connect to an Oracle 7.2 database, mainly for lack of documentation of exactly what changes to make and where. I kept getting the message "Vendor Initialization failed: ORANT71.DLL". A call to Borland revealed that I needed to use the BDE Config tool to blank out the Server Name field, change the Net Protocol to the value "2," and alter the vendor init entry to ORA72.DLL. Yeah, I know, it should have been obvious....

User-Interface Development

Developing user interfaces for database apps requires laying out controls that are tied to database columns. With grid controls, picture controls, and graphic elements, it's difficult to lay everything out correctly unless you can see how the actual database data looks on the form. Borland implements the concept of "live data" with Delphi, which enables data-aware controls to connect to the underlying database right at design time. You don't have to compile or run any code to see the results in a form.

The steps for creating live data forms are straightforward. Create a TTable or TQuery component on the form. You set its DatabaseName property to reference your database. You set the TableName to indicate which table in the database you want to use, and set the Active property to True. That's it. Anything attached to the TTable or TQuery after that will get data from the database table at design time. To access the data, you create a TDataSource component and set its DataSet property to the name of the TTable or TQuery component to use.

You can scroll through the database data with the grid scrollbar to see all the records located in a table or returned by a query. Seeing your data is not only convenient for layout purposes, it lets you verify immediately whether the form is retrieving the data you really want. All the database controls support live data.

By default, connecting a grid to a data source makes the grid create columns for all the fields returned by the associated TTable or TQuery. The Object Inspector provides a Columns property that gives you access to a full Column editor, allowing you to eliminate columns you don't need. The editor also lets you change the column titles, widths, read-only status, color, and more. You can also make the grid control display check boxes or drop-down comboboxes in a grid field.

Report Writing

Many Delphi applications use simple forms with data-aware controls and grids to show database data, but medium and large applications nearly always need reports to present information. Delphi has a built-in report generator, called "ReportSmith", that enables you to create formatted reports in a completely graphical environment. ReportSmith lets you create reports that have formatted text fields, pictures, and graphical elements such as boxes and lines. The program has a Wizard that knows how to create columnar, crosstab, form, and label types. Starting from a default report created by the Wizard, you can create reports often without programming. For specialized reports, there is an integrated SQL editor that lets you enter your own SQL Select statements.

ReportSmith produces fine printed reports. You can also export the report data in a number of file formats, including Excel, Lotus 1-2-3, and Quattro Pro. I was disappointed that rich-text format (RTF) wasn't supported because I often include parts of reports in word-processing documents. Nor can you cut-and-paste to select parts of a report, because ReportSmith doesn't allow you to copy parts of a report to the clipboard. On the other hand, ReportSmith does produce labels, something many high-end report writers don't do.

Writing a Multithreaded Program

To give you a taste of Delphi programming, I'll present a sample multithreaded program (available electronically; see "Availability, page 3). With C or C++, you'd expect to deal with lots of low-level Windows API calls to write a multithreaded app. Of course you can get down and dirty in Delphi, but in most cases, only if there's no alternative.

My multithreaded program, called "Bounce," has three objects bouncing around inside separate regions of a window. Each object is a black square with a colored body. As it moves, it leaves a black wake, eventually painting over the entire bounce region. Figure 3 shows how Bounce looks after running awhile.

Each object starts off in a random position, moving downward and to the right. When it hits the boundary of the enclosing region, it bounces off and continues. The Start and Stop buttons control the three threads. The Stop button suspends them, the Start button makes them resume. Each object is animated by a separate thread, implemented as a class derived from the VCL class TThread; see Listing Eight. The constructor for class TThreadBouncingObject randomizes the initial position for each bouncing object, and saves a pointer to the containment bounce region. The thread execution is carried out by the Execute method, which runs an infinite loop moving the object. At the end is a Sleep call, which suspends the thread briefly, to slow down the bouncing objects: On my 100-MHz Pentium, the objects were bouncing around so fast I couldn't tell what they were doing.

The main form creates three side-by-side regions of type TPanel, which are used as the containment boxes for the bouncing objects. The thread objects are created inside the FormCreate member, and destroyed in the FormDestroy member. These two functions are similar to regular constructors and destructors, except they are called as handlers by Delphi. The code for the main form is shown in Listing Nine.

Objects are created in two phases in Delphi. The declaration itself isn't enough, as in C++. You actually have to call an object's constructor to initialize it. The arrangement is similar to the way GDI objects are handled by Microsoft Visual C++, where you declare things like CPens and CBrushes, and subsequently must invoke their Create members.

Conclusion

The support for database exceptions, live data, grids, and BLOBs make Delphi 2.0 nearly an ideal database-development environment. Delphi makes it possible to develop local database applications that can easily be converted into full client/ server apps.

For More Information

Borland International

100 Borland Way

Scotts Valley, CA 95066

800-331-0877

http://www.borland.com

Figure 1: The Delphi IDE.

Figure 2: The architecture of a Delphi database application.

Figure 3: Running the Bounce program.

Listing One

TMyThread = class(TThread)
protected
    procedure Execute; override;
public
    constructor Create(your parameters} );
  end;

Listing Two

type
  TMyForm = class(TForm)
private
  LengthyOperation: TMyThread;
  end;

Listing Three

procedure TMyForm.StartButtonClick(Sender: TObject);
begin
  LengthyOperation:= TMyThread.Create( your parameter list);
end;

Listing Four

procedure TMyForm. AbortButtonClick(Sender: TObject);
begin
  LengthyOperation.Terminate;
end;

Listing Five

procedure TShape.Paint;
begin
  with Canvas do
        Rectangle(100, 100, 200, 200);
end;

Listing Six

procedure TMyForm.WndProc(var Message: TMessage);
begin
  case Message.Msg of
   WM_LBUTTONDOWN,  WM_CHAR:
      {do something}
      Exit;
  end;
  inherited;
end; 

Listing Seven

procedure TForm1.Button1Click(Sender: TObject);
begin
end;

Listing Eight

unit ThreadTest;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, 
  Controls, Forms, Dialogs, ExtCtrls;
type
  TThreadBouncingObject = class(TThread)
  private
    ContainmentBox: TPaintBox;
    BoundsCheckBox: TRect;
    P: TPoint;
    XIncrement, YIncrement: Integer;
    procedure UpdateScreen;
  protected
    procedure Execute; override;
  public
    constructor Create(Box: TPaintBox);
  end;
const
  WIDTH = 10;
  HEIGHT = 10;
implementation
{$R *.DFM}
constructor TThreadBouncingObject.Create(Box: TPaintBox);
begin
  inherited Create(True);
  ContainmentBox := Box;
  BoundsCheckBox := Rect(0, 0, Box.Width - WIDTH, Box.Height - HEIGHT);
  P.X := Random(ContainmentBox.Width - WIDTH);
  P.Y := Random(ContainmentBox.Height - HEIGHT);
  XIncrement := 1;
  YIncrement := 1;
end;
procedure TThreadBouncingObject.Execute;
begin
  repeat
    begin
      {update the X position}
      P.X := P.X + XIncrement;
      if (XIncrement > 0) and (P.X >= BoundsCheckBox.Right) or
         (XIncrement < 0) and (P.X <= BoundsCheckBox.Left) then
        XIncrement := -XIncrement;
        
      {update the Y position}
      P.Y := P.Y + YIncrement;
      if (YIncrement > 0) and (P.Y >= BoundsCheckBox.Bottom) or
         (YIncrement < 0) and (P.Y <= BoundsCheckBox.Top) then
        YIncrement := -YIncrement;
      UpdateScreen;
      Sleep(10); {pause briefly}
    end
  until Terminated;
end;
procedure TThreadBouncingObject.UpdateScreen;
begin
    ContainmentBox.Canvas.Rectangle(P.X, P.Y, P.X + WIDTH, P.Y + HEIGHT);
end;
end.

Listing Nine

unit TestForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ThreadTest, StdCtrls;
type
  TForm3Threads = class(TForm)
    Panel1: TPanel;
    PaintBox1: TPaintBox;
    PaintBox2: TPaintBox;
    PaintBox3: TPaintBox;
    ButtonStart: TButton;
    ButtonStop: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure ButtonStartClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ButtonStopClick(Sender: TObject);
  private
    Object1: TThreadBouncingObject;
    Object2: TThreadBouncingObject;
    Object3: TThreadBouncingObject;
  public
    { Public declarations }
  end;
var
  Form3Threads: TForm3Threads;
implementation
{$R *.DFM}
procedure TForm3Threads.FormDestroy(Sender: TObject);
begin
  Object1.Terminate;
  Object1.Free;
  Object2.Terminate;
  Object2.Free;
  Object3.Terminate;
  Object3.Free;
end;
procedure TForm3Threads.ButtonStartClick(Sender: TObject);
begin
  Object1.Resume;
  Object2.Resume;
  Object3.Resume;
end;
procedure TForm3Threads.FormCreate(Sender: TObject);
begin
  Randomize;   {for the object initial positions}
  Object1 := TThreadBouncingObject.Create(PaintBox1);
  Object2 := TThreadBouncingObject.Create(PaintBox2);
  Object3 := TThreadBouncingObject.Create(PaintBox3);
end;
procedure TForm3Threads.ButtonStopClick(Sender: TObject);
begin
  Object1.Suspend;
  Object2.Suspend;
  Object3.Suspend;
end;
end.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.