TOCBACKFORWARD

Charlie Calvert's C++ Builder Unleashed

- 23 -

Creating Components from Scratch

In this chapter, you learn how to use custom-made components to help create programs that solve at least a minimal set of real-world problems. In particular, the program demonstrated in this chapter provides a minimal implementation of a warehouse-based inventory program. This chapter also covers designing components and screens that mirror objects in the real world, as well as creating your own event handlers.

Much of the material in this chapter follows naturally from the subject matter in the preceding chapter. However, the code shown here is more advanced, and the text looks a little more deeply into the theories involved in creating powerful, reusable components.

A program called Warehouse forms the core of this chapter. Warehouse uses simple graphic objects to depict a warehouse in which several different kinds of widgets are stored. There are seven panels in this warehouse, each containing from 4 to 12 pallets full of widgets. You are able to stock each pallet with new widgets and ask questions about the stock stored in the warehouse.

The program in this chapter builds on the object hierarchy discussed in Chapter 19, "Inheritance," in the program called OBJECT1. The new versions of this hierarchy advances the code to the point where it can be used for the relatively practical tasks just described. OOP, which seems highly theoretical and intangible at first glance, actually turns out to be a natural tool for tracking and depicting the status of real objects, such as the inventory in a warehouse.

Warehouse

As described at the beginning of this chapter, Warehouse is a simulation that features a series of panels arranged in a large room. Each panel has from 4 to 12 pallets on it, and each pallet contains a certain number of widgets. Users can stock additional widgets on the pallets by dragging and dropping them from a central gateway that leads into the warehouse. The idea is that the widgets are being transferred from the point of manufacture to a place where they can be stored before being sold.


NOTE: The Warehouse program shows the outlines of a clean approach to a real-world problem. However, it is not a complete business application. It shows how objects can be used to embody actual entities that we find in day-to-day life, but it's a theoretical example and not a real-world program.

To bring this program up to the point where it might be used in an office would be a considerable chore that would involve adding both features and error checking. However, in the construction of any large project, one key step is the development of a methodology that can support a large application. The foundation for a useful program is found in Warehouse. It shows a lot about the way any good OOP tool should be constructed.

The Warehouse program is stocked with hypothetical widgets named TWidget, TPentium, and TPentiumPro. Another application might have TChairs, TTables, TBureaus, and so on, instead of chips. The issue is not the names or traits of the individual widgets but the fact that a series of different TWidget descendants needs to be created.

You will find that TPentium and TPentiumPro share many traits. This is a somewhat artificial aspect of this program; in a real-world application, each of these objects would be more varied. For instance, a TChair would have an FLegs data store, a TBed would have an FFrame data store, and so on.


NOTE: When studying Warehouse, you will find that the TWidget object has changed slightly from its appearance in OBJECT3. These types of minor structural changes should be expected in OOP. Developers don't start with a base class and build up a hierarchy without ever deciding that changes need to be made to the structures they are creating. Frameworks and hierarchies evolve over time; they do not burst into the world fully formed.

If you are building a certain kind of tool, after the first release, it is very dangerous to go back and start changing the way base classes work. Nevertheless, during development, changes need to be made to the base classes in your hierarchy. This is one of the major "gotchas" of object-oriented programming, and I would be remiss if I didn't lay it out in clear terms. Right now, there are no good remedies for this problem, but the lesson it teaches is clear: Don't release objects to unsuspecting programmers until you are sure you have the proper design! If you are looking for some relief, my experience has shown that most major companies don't think beta testers fit the definition of "unsuspecting."

Furthermore, to the best of your ability, you should do everything possible to hide the actual implementation of your object from your user. Use properties heavily. Hide your data. Create public functions or even public objects that call the functions and objects that are part of your real implementation. Nobody ever hides their implementation completely, but it is usually better to err on the side of safety, rather than striving always to save a few bytes of memory.

The Warehouse program features one main form and two child forms used to display reports on the data in the warehouse. The main form depicts the floor of the warehouse, as shown in Figure 23.1. The left-center of the main form contains three widgets you can drag onto the various pallets by using the mouse. To do this, left-click on one of the widgets and drag the mouse over one of the pallets. When you release the mouse, a dialog box pops up prompting you for the number of widgets of a particular type you want to drop on a pallet. You can type in a number and then click OK, as shown in Figure 23.2.

FIGURE 23.1. The Warehouse form shows the floor of the warehouse.

FIGURE 23.2. Specifying the number of items you want to drop on a particular pallet.

If you select File | Show Table from the menu on the main form, you can see a table that lists all the items in the warehouse, as shown in Figure 23.3. If you select Options | List by Product from the menu, you get a report on all the products in the warehouse, as shown in Figure 23.4. If you select Options | List by Pallet from the menu, you get a report on all the pallets in the warehouse, as shown in Figure 23.5.

FIGURE 23.3. The Report form shows the status of the objects on an individual pallet.

FIGURE 23.4. A report on all the products in the warehouse, including their count and value.

FIGURE 23.5. A report on all the pallets in the warehouse, including the number of widgets they contain and the value of the widgets.

If you right-click on a particular pallet, you can bring up a popup menu that lets you explore the contents of a particular pallet. For instance, you can see the items on a pallet, as shown in Figure 23.6, or you can see the value of the items on a particular pallet, as shown in Fig- ure 23.7.

Because of time constraints, I have not added any provisions to the program for selling items. However, you do have a fairly interesting simulation that models a portion of a business.

Perhaps the most interesting thing about the program is the small amount of top-level code needed to create it, as shown in Listing 23.1.

FIGURE 23.6. A report on the items found on a particular pallet.

FIGURE 23.7. A report on the value of the items found on a particular pallet.

Listing 23.1. The project file for the Warehouse program is listed here; the rest of the program is found on the CD.

///////////////////////////////////////

// 
Warehouse.cpp

// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop

USEFORM("Main.cpp", Form1);

USERES("WareHouse.res");


USEUNIT("\SrcC\PUnleash\Utils\MyObject.cpp");

USEUNIT("\SrcC\PUnleash\Utils\Widgets.cpp");

USEDATAMODULE("DMod1.cpp", DMod);

USEFORM("DataForm1.cpp", DataForm);

USEFORM("QueryForm1.cpp", 
QueryForm);

USEFORM("HierarchyDlg1.cpp", HierarchyDlg);

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{

  try

  {

    Application->Initialize();

    Application->CreateForm(__classid(TForm1), &Form1);

    
Application->CreateForm(__classid(TDMod), &DMod);

    Application->CreateForm(__classid(TDataForm), &DataForm);

    Application->CreateForm(__classid(TQueryForm), &QueryForm);

    
Application->CreateForm(__classid(THierarchyDlg), &HierarchyDlg);

    Application->Run();

  }

  catch (Exception &exception)

  {

    Application->ShowException(&exception);

  }

  return 0;

}

If you wind your way down the hierarchy of objects used in this program (see Listings 23.2 through 23.15), there is, of course, a considerable amount of complexity. But from the point of view of the application developer, the code for this program is very easy to create. Most of the complexity is in the VCL itself, and some degree of complexity is hidden in the TWidget and TPallet objects created in this chapter, and in some of the earlier chapters. But at the top level, where you find the main modules for this program, almost all the code is sparse, easy to understand, and simple to maintain. This is the right way to leverage objects so they can help you get a lot of high-quality work done quickly.

Listing 23.2. The header file for the main program.

///////////////////////////////////////

// Main.h

// Warehouse example: learning about objects

// Copyright 
(c) 1997 by Charlie Calvert

//

#ifndef MainH

#define MainH

#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\ExtCtrls.hpp>

#include 
<vcl\Buttons.hpp>

#include <vcl\Menus.hpp>

#include "Widgets.h"



class TForm1 : public TForm

{

__published:

  TPanel *Panel1;

  TPanel *Panel2;

  TPanel *Panel3;

  TPanel *Panel4;

  TDataPallet *sp41;

  TDataPallet 
*Sp42;

  TDataPallet *Sp43;

  TDataPallet *Sp44;

  TDataPallet *Sp45;

  TDataPallet *Sp46;

  TDataPallet *Sp47;

  TDataPallet *Sp48;

  TDataPallet *Sp49;

  TDataPallet *Sp410;

  TPanel *Panel5;

  TDataPallet *DataPallet10;

  TPanel *Panel6;

  
TPanel *Panel7;

  TMainMenu *A;

  TMenuItem *List;

  TPentiumPro *PentiumPro;

  TPentium *Pentium;

  TWidget *Widget;

  TDataPallet *DataPallet35;

  TMenuItem *File1;

  TMenuItem *ShowTable1;

  TMenuItem *N1;

  TMenuItem *Exit1;

  
TPopupMenu *PopupMenu1;

  TMenuItem *ItemsOnPallet1;

  TMenuItem *ValueofItems1;

  TMenuItem *Options1;

  TMenuItem *ListbyPallet1;

  TMenuItem *GetHierarchy1;

  TLabel *Label1;

  TLabel *Label2;

  void __fastcall SpeedButton1DragOver(TObject 
*Sender, TObject *Source, int X,

                                      int Y, TDragState State, bool &Accept);

  void __fastcall WidgetMouseDown(TObject *Sender, TMouseButton Button,

                                 TShiftState Shift, int X, 
int Y);

  void __fastcall Exit1Click(TObject *Sender);

  void __fastcall ShowTable1Click(TObject *Sender);

  void __fastcall DataPallet10MouseDown(TObject *Sender, TMouseButton Button,

                                       TShiftState Shift, int 
X, int Y);

  void __fastcall ItemsOnPallet1Click(TObject *Sender);

  void __fastcall ValueofItems1Click(TObject *Sender);

  void __fastcall ListClick(TObject *Sender);

  void __fastcall ListbyPallet1Click(TObject *Sender);

  void __fastcall 
GetHierarchy1Click(TObject *Sender);

private:

public:

  virtual __fastcall TForm1(TComponent* Owner);

};



extern TForm1 *Form1;



#endif



Listing 23.3. The main form for the Warehouse program.

///////////////////////////////////////

// Main.cpp

// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop


#include "Main.h"

#include "DMod1.h"

#include "DataForm1.h"

#include "QueryForm1.h"

#include "HierarchyDlg1.h"

#pragma link "Widgets"

#pragma resource "*.dfm"

TForm1 *Form1;




__fastcall TForm1::TForm1(TComponent* Owner)

	                                                        : TForm(Owner)

{

}



void __fastcall TForm1::SpeedButton1DragOver(TObject *Sender, TObject *Source,

	                         int X, int Y, 
TDragState State, bool &Accept)

{

 if (!dynamic_cast<TWidget *>(Source))

    Accept = False;

}



void __fastcall TForm1::WidgetMouseDown(TObject *Sender, TMouseButton Button,

	                               TShiftState Shift, int X, 
int Y)

{

  if (!dynamic_cast<TWidget *>(Sender))

    return;



  TWidget *W = (TWidget *)(Sender);

  

  if (Shift.Contains(ssLeft))

  {

    W->BeginDrag(False);

  }

  else

  {

    HierarchyDlg->ListBox1->Items = 
W->GetHierarchy();

    HierarchyDlg->ShowModal();

  }

}



void __fastcall TForm1::ShowTable1Click(TObject *Sender)

{

  DataForm->Show();

}



void __fastcall TForm1::Exit1Click(TObject *Sender)

{

  Close();

}



void __fastcall 
TForm1::DataPallet10MouseDown(TObject *Sender,

  TMouseButton Button, TShiftState Shift, int X, int Y)

{

  if (Shift.Contains(ssLeft))

  {

    if (dynamic_cast<TDataPallet *>(Sender))

    {

      TDataPallet *D = (TDataPallet *)(Sender);

      
D->QueryPallet(DMod->WidgetsQuery);

      QueryForm->Panel1->Caption =

        "List of Items on Pallet: " + AnsiString(D->PalletNumber);

      QueryForm->ShowModal();

    }

  }

}



void __fastcall 
TForm1::ItemsOnPallet1Click(TObject *Sender)

{

  TDataPallet *D = (TDataPallet *)PopupMenu1->PopupComponent;

  DataPallet10MouseDown(D, TMouseButton(), TShiftState() << ssLeft, 0, 0);

}



void __fastcall 
TForm1::ValueofItems1Click(TObject *Sender)

{

  TDataPallet *D = (TDataPallet *)PopupMenu1->PopupComponent;

  D->QueryPalletSum(DMod->WidgetsQuery);

  QueryForm->Panel1->Caption =

    "Value of Items on Pallet: " + 
AnsiString(D->PalletNumber);

  QueryForm->ShowModal();

}



void __fastcall TForm1::GetHierarchy1Click(TObject *Sender)

{

  TDataPallet *D = (TDataPallet *)PopupMenu1->PopupComponent;

  HierarchyDlg->ListBox1->Items = 
D->GetHierarchy();

  HierarchyDlg->ShowModal();

}



void __fastcall TForm1::ListClick(TObject *Sender)

{

  DMod->SumByProduct();

  QueryForm->Panel1->Caption = "Sum By Product of Entire Warehouse";

  
QueryForm->ShowModal();

}



void __fastcall TForm1::ListbyPallet1Click(TObject *Sender)

{

  DMod->ReportByPallet();

  QueryForm->Panel1->Caption = "Report By Pallet";

  QueryForm->ShowModal();

}



Listing 23.4. The header file for the Widgets unit.

///////////////////////////////////////

// Widgets.h

// Learning how to use objects

// Copyright (c) 1997 by 
Charlie Calvert

//



#ifndef WidgetsH

#define WidgetsH

#include <vcl\dbtables.hpp>

#include "myobject.h"





TCustomControl *ReadWidgetFromStream(AnsiString StreamName);

void WriteWidgetToStream(AnsiString StreamName, 
TCustomControl *Widget);



class __declspec(delphiclass) TWidget;



namespace Widgets

{

  void __fastcall RegisterShort();

  void __fastcall Register();

}



class TWidget: public TCustomControl

{

private:

  TListHierarchy *Hierarchy;

  
Currency FCost;

  TDateTime FTimeCreated;

  AnsiString FDescription;

  void __fastcall SetTimeCreated(AnsiString S);

  AnsiString __fastcall GetTimeCreated();

protected:

  virtual void __fastcall Paint(void);

public:

  __fastcall virtual 
TWidget(TComponent *AOwner): TCustomControl(AOwner)

    { Hierarchy = new TListHierarchy(); Width = 25; Height = 25; }

  __fastcall virtual TWidget(TComponent *AOwner, int ACol, int ARow);

  __fastcall virtual ~TWidget() { delete Hierarchy; }

  
TStringList *GetHierarchy() { return Hierarchy->GetHierarchy(this); }

  void __fastcall WidgetMouseDown(TObject *Sender, TMouseButton Button,

                                 TShiftState Shift, int X, int Y);

__published:

  __property Currency 
Cost={read=FCost, write=FCost};

  __property AnsiString TimeCreated={read=GetTimeCreated, write=SetTimeCreated};

  __property AnsiString Description={read=FDescription, write=FDescription};

  __property OnDragDrop;

  __property OnMouseDown;

};




class TChip: public TWidget

{

public:

  virtual __fastcall TChip(TComponent *AOwner): TWidget(AOwner) {}

  virtual __fastcall TChip(TComponent *AOwner, int ACol, int ARow)

    : TWidget(AOwner, ACol, ARow) {}

};



class TPentium: public TChip


{

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall TPentium(TComponent *AOwner): TChip(AOwner) {}

  virtual __fastcall TPentium(TComponent *AOwner, int ACol, int ARow)

    : TChip(AOwner, ACol, ARow) {}

};



class 
TPentiumPro: public TChip

{

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall TPentiumPro(TComponent *AOwner): TChip(AOwner) {}

  virtual __fastcall TPentiumPro(TComponent *AOwner, int ACol, int ARow)

    : 
TChip(AOwner, ACol, ARow) {}

};



class TCustomPallet: public TCustomControl

{

private:

  int FPalletNumber;

  TListHierarchy *FHierarchy;

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall 
TCustomPallet(TComponent *AOwner);

  virtual __fastcall ~TCustomPallet(void)

    { delete FHierarchy; }

  TStringList *GetHierarchy() { return FHierarchy->GetHierarchy(this); }

  __property int PalletNumber={read=FPalletNumber, 
write=FPalletNumber};

};



class TPallet: public TCustomPallet

{

public:

  virtual __fastcall TPallet(TComponent *AOwner): TCustomPallet(AOwner)

    { Width = 25; Height = 25; }

__published:

  __property PalletNumber;

  __property OnDragDrop;

  
__property OnDragOver;

  __property Color;

};



class TDataPallet: public TCustomPallet

{

private:

  TTable *FWidgetsTable;

  TQuery *FWidgetsQuery;

protected:

  void __fastcall PalletDragDrop(TObject *Sender,

    TObject *Source, int X, int 
Y);

  void __fastcall PalletDragOver(TObject *Sender, TObject *Source,

    int X, int Y, TDragState State, bool &Accept);

  void virtual EnterWidgets(int Total, TWidget *W);

public:

  virtual __fastcall TDataPallet(TComponent *AOwner);

  
void __fastcall QueryPallet(TQuery *Query);

  float __fastcall QueryPalletSum(TQuery *Query);

__published:

  __property TTable *WidgetsTable={read=FWidgetsTable, write=FWidgetsTable};

  __property TQuery *WidgetsQuery={read=FWidgetsQuery, 
write=FWidgetsQuery};

  __property PalletNumber;

  __property Hint;

  __property Color;

  __property TabOrder;

  __property OnMouseDown;

  __property PopupMenu;

};



#endif



Listing 23.5. The main source file for the Widgets unit.

///////////////////////////////////////

// Widgets.cpp

// Learning how to use objects

// Copyright (c) 1997 by Charlie Calvert

//


#include <vcl\vcl.h>

#include <conio.h>

#pragma hdrstop

#include "widgets.h"



#pragma link "MyObject.obj"



void WriteWidgetToStream(AnsiString StreamName, TCustomControl *Widget)

{

  TFileStream *Stream = new 
TFileStream(StreamName, fmCreate | fmOpenWrite);

  Stream->WriteComponent(Widget);

  delete Stream;

}



TCustomControl *ReadWidgetFromStream(AnsiString StreamName)

{

  Widgets::RegisterShort();

  TFileStream *Stream = new 
TFileStream(StreamName, fmOpenRead);

  TWidget *Widget = (TWidget *)Stream->ReadComponent(NULL);

  delete Stream;

  return Widget;

}



__fastcall TWidget::TWidget(TComponent *AOwner, int ACol, int ARow)

  : TCustomControl(AOwner)

{

  
Hierarchy = new TListHierarchy();

  Left = ACol;

  Top = ARow;

  Width = 25;

  Height = 25;

  OnMouseDown = WidgetMouseDown;

}



AnsiString __fastcall TWidget::GetTimeCreated()

{

  return FTimeCreated.DateTimeString();

}



void __fastcall 
TWidget::SetTimeCreated(AnsiString S)

{

  FTimeCreated = TDateTime(S);

}



void __fastcall TWidget::Paint()

{                     

  Canvas->Brush->Color = clBlue;

  Canvas->Rectangle(0, 0, ClientWidth, ClientHeight);

  
Canvas->Brush->Color = clYellow;

  Canvas->Ellipse(0, 0, ClientWidth, ClientHeight);

}



void __fastcall TWidget::WidgetMouseDown(TObject *Sender, TMouseButton Button,

                                        TShiftState Shift, int X, int 
Y)

{

  ShowMessage(Format("%m", OPENARRAY(TVarRec, (FCost))));

}



void __fastcall TPentium::Paint()

{

  Canvas->Brush->Color = clPurple;

  Canvas->Rectangle(0, 0, ClientWidth, ClientHeight);

  Canvas->Brush->Color = 
clRed;

  Canvas->Ellipse(0, 0, ClientWidth, ClientHeight);

}



void __fastcall TPentiumPro::Paint()

{                     

  Canvas->Brush->Color = clGreen;

  Canvas->Rectangle(0, 0, ClientWidth, ClientHeight);

  
Canvas->Brush->Color = clBlue;

  Canvas->Ellipse(0, 0, ClientWidth, ClientHeight);

}





__fastcall TCustomPallet::TCustomPallet(TComponent *AOwner)

  : TCustomControl(AOwner)

{

  Width = 25;

  Height = 25;

  Color = clBlue;

  
FHierarchy = new TListHierarchy();

}



void __fastcall TCustomPallet::Paint(void)

{

  Canvas->Brush->Color = Color;

  Canvas->Rectangle(0, 0, ClientWidth, ClientHeight);

}



__fastcall TDataPallet::TDataPallet(TComponent *AOwner)

: 
TCustomPallet(AOwner)

{

  OnDragOver = PalletDragOver;

  OnDragDrop = PalletDragDrop;

}



void __fastcall TDataPallet::PalletDragOver(TObject *Sender,

  TObject *Source, int X, int Y, TDragState State, bool &Accept)

{

  Accept = 
dynamic_cast<TWidget *>(Source);

}



void __fastcall TDataPallet::PalletDragDrop(TObject *Sender,

  TObject *Source, int X, int Y)

{

  if (WidgetsTable == NULL)

    ShowMessage("No table assigned to the WidgetsTable property");

  
else

  {

    if (dynamic_cast<TWidget *>(Source))

    {

      TWidget *W = (TWidget *)(Source);

      AnsiString S = "Enter number of " + W->Name;

      AnsiString NumWidgets;

      

      if (InputQuery("Widget Number 
Dialog", S, NumWidgets))

      {

        EnterWidgets(NumWidgets.ToInt(), W);

      }

    }

  }

}



void TDataPallet::EnterWidgets(int Total, TWidget* W)

{

  int i;



  for (i = 0; i < Total; i++)

  {

    
FWidgetsTable->Insert();

    FWidgetsTable->FieldByName("Name")->AsString = W->Name;

    FWidgetsTable->FieldByName("Created")->AsString = W->TimeCreated;

    
FWidgetsTable->FieldByName("Description")->AsString = W->Description;

    FWidgetsTable->FieldByName("Cost")->AsCurrency = W->Cost;

    FWidgetsTable->FieldByName("PalletNumber")->AsInteger = 
PalletNumber;

    FWidgetsTable->Post();

  }

}



void __fastcall TDataPallet::QueryPallet(TQuery *Query)

{

  AnsiString S = "Select * from Widgets where PalletNumber = " +

    AnsiString(PalletNumber);

  Query->SQL->Clear();

  
Query->SQL->Add(S);

  Query->Open();

}



float __fastcall TDataPallet::QueryPalletSum(TQuery *Query)

{

  AnsiString S = "Select Sum(Cost) from Widgets where PalletNumber = " +

    AnsiString(PalletNumber);

  
Query->SQL->Clear();

  Query->SQL->Add(S);

  Query->Open();

  return Query->Fields[0]->AsFloat;

}



namespace Widgets

{

  void __fastcall RegisterShort()

  {

    TComponentClass classes[4] = {__classid(TWidget),

       
__classid(TPentium), __classid(TPentiumPro),

       __classid(TPallet) };

    RegisterClasses(classes, 3);

  }



  void __fastcall Register()

  {

    TComponentClass classes[5] = {__classid(TWidget),

       __classid(TPentium), 
__classid(TPentiumPro),

       __classid(TPallet), __classid(TDataPallet) };

    RegisterComponents("Unleash", classes, 4);

  }

}



Listing 23.6. The final take on the header for the MyObject unit.

///////////////////////////////////////

// MyObject.h

// Learning how to use objects

// Copyright (c) 1997 by Charlie Calvert

//



#ifndef MyObjectH

#define MyObjectH




#include <conio.h>

#include <vcl\stdctrls.hpp>



class TMyObject :public TObject

{

protected:

  virtual void PrintString(AnsiString S);

public:

  TMyObject() : TObject()  {}

  void ShowHierarchy(TObject *AnObject);

};



class 
TListHierarchy: public TMyObject

{

private:

  TStringList *FList;

protected:

  virtual void PrintString(AnsiString S)

    { FList->Add(S); }

public:

  TListHierarchy() { FList = new TStringList(); }

  __fastcall virtual ~TListHierarchy() { 
delete FList; }

  TStringList *GetHierarchy(TObject *AnObject)

    { FList->Clear(); ShowHierarchy(AnObject); return FList; } 

};



class __declspec(delphiclass) TVCLHierarchy;



class THierarchy: public TMyObject

{

  friend TVCLHierarchy;

  
int FTextColor;

  int FBackColor;

protected:

  virtual __fastcall void SetTextColor(int Color)

    { FTextColor = Color; textcolor(FTextColor); }

  virtual __fastcall void SetBackColor(int Color)

    { FBackColor = Color; 
textbackground(FBackColor); }

public:

  THierarchy() : TMyObject() {}

  virtual void PrintString(AnsiString S);

  virtual void ClrScr();

  __property int TextColor={read=FTextColor,write=SetTextColor};

  __property int 
BackColor={read=FBackColor,write=SetBackColor};

};



class TVCLHierarchy : public THierarchy

{

  TMemo *FMemo;

protected:

  virtual __fastcall void SetTextColor(int Color)

    { FTextColor = Color; FMemo->Font->Color = TColor(FTextColor); 
}

  virtual __fastcall void SetBackColor(int Color);



public:

  TVCLHierarchy(TMemo *AMemo): THierarchy() { FMemo = AMemo; }

  virtual void PrintString(AnsiString S)

    { FMemo->Lines->Add(S); }

  virtual void ClrScr() { 
FMemo->Clear(); }

};



#endif



Listing 23.7. The final take on the main source file for the MyObject unit.

///////////////////////////////////////

// MyObject.cpp

// Learning how to use objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#include <conio.h>

#pragma hdrstop

#include 
"myobject.h"



void TMyObject::PrintString(AnsiString S)

{

  printf("%s\n", S.c_str());

}



void TMyObject::ShowHierarchy(TObject *AnObject)

{

  TClass AClass;



  AnsiString AClassName = 
AnsiString(AnObject->ClassName()).c_str();

  PrintString(AClassName);



  AClass = AnObject->ClassParent();

  while (True)

  {

    AClassName = AnsiString(AClass->ClassName());

    PrintString(AClassName);

    if (AClassName == 
"TObject")

      break;

    AClass = AClass->ClassParent();

  }

}



void THierarchy::PrintString(AnsiString S)

{

  char Temp[250];



  sprintf(Temp, "%s\n\r", S.c_str());

  cputs(Temp);

}



void THierarchy::ClrScr()


{

  clrscr();

}



void __fastcall TVCLHierarchy::SetBackColor(int Color)

{

  FBackColor = Color;

  FMemo->Color = TColor(FBackColor);

}



Listing 23.8. The header file for the programs data module.

///////////////////////////////////////

// DMod1.h

// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#ifndef DMod1H

#define DMod1H


#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\DBTables.hpp>

#include <vcl\DB.hpp>



class TDMod : public TDataModule

{

__published:

  
TTable *WidgetsTable;

  TDataSource *WidgetsTableSource;

  TQuery *WidgetsQuery;

  TDataSource *WidgetsQuerySource;

  void __fastcall DModCreate(TObject *Sender);

private:

public:

  virtual __fastcall TDMod(TComponent* Owner);

  void 
__fastcall SumByProduct();

  void __fastcall ReportByPallet();

};



extern TDMod *DMod;



#endif



Listing 23.9. The main source file for the data module.

///////////////////////////////////////

// DMod1.cpp

// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop

#include "DMod1.h"

#pragma 
resource "*.dfm"

TDMod *DMod;



fastcall TDMod::TDMod(TComponent* Owner)

  : TDataModule(Owner)

{

}



void __fastcall TDMod::DModCreate(TObject *Sender)

{

  WidgetsTable->Open();  

}



void __fastcall TDMod::SumByProduct()

{

  
AnsiString S = "Select Name, Count(*), Sum(Cost) "

                 "from Widgets "

                 "group by Name;";

  WidgetsQuery->SQL->Clear();

  WidgetsQuery->SQL->Add(S);

  WidgetsQuery->Open();


}



void __fastcall TDMod::ReportByPallet()

{

  AnsiString S = "Select PalletNumber, Name, Count(*), Sum(Cost) "

                 "from Widgets "

                 "group by PalletNumber, Name;";

  
WidgetsQuery->SQL->Clear();

  WidgetsQuery->SQL->Add(S);

  WidgetsQuery->Open();

}



Listing 23.10. The header file for the very simple QueryForm.

#ifndef QueryForm1H

#define QueryForm1H

#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\DBGrids.hpp>

#include 
"Grids.hpp"

#include <vcl\ExtCtrls.hpp>



class TQueryForm : public TForm

{

__published:

  TDBGrid *DBGrid1;

  TPanel *Panel1;

private:

public:

  virtual __fastcall TQueryForm(TComponent* Owner);

};



extern TQueryForm 
*QueryForm;



#endif



Listing 23.11. There is no custom code in the QueryForm.

///////////////////////////////////////

// QueryForm1.cpp


// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop

#include "QueryForm1.h"

#include "DMod1.h"

#pragma link "Grids"

#pragma resource 
"*.dfm"

TQueryForm *QueryForm;



__fastcall TQueryForm::TQueryForm(TComponent* Owner)

  : TForm(Owner)

{

}



Listing 23.12. The header for the very simple DataForm.

///////////////////////////////////////

// DataForm1.h

// Warehouse example: learning about objects

// Copyright (c) 1997 by Charlie Calvert

//

#ifndef DataForm1H

#define DataForm1H

#include 
<vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\DBGrids.hpp>

#include "Grids.hpp"

#include <vcl\ExtCtrls.hpp>



class TDataForm : 
public TForm

{

__published:

  TDBGrid *DBGrid1;

  TPanel *Panel1;

private:

public:

  virtual __fastcall TDataForm(TComponent* Owner);

};



extern TDataForm *DataForm;



#endif



Listing 23.13. The main module for the DataForm contains no custom code.

///////////////////////////////////////

// DataForm1.cpp

// Warehouse example: learning about objects

// Copyright 
(c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop

#include "DataForm1.h"

#include "DMod1.h"

#pragma link "Grids"

#pragma resource "*.dfm"

TDataForm *DataForm;



__fastcall 
TDataForm::TDataForm(TComponent* Owner)

  : TForm(Owner)

{

}



Listing 23.14. The header file for a form that provides a list box for displaying the hierarchy of objects.

///////////////////////////////////////

// HierarchyDlg.h

// Learning how to use objects

// Copyright (c) 1997 by Charlie Calvert

//

#ifndef HierarchyDlg1H

#define HierarchyDlg1H

#include 
<vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

#include <vcl\ExtCtrls.hpp>

#include <vcl\Buttons.hpp>



class THierarchyDlg : public TForm

{

__published:

  
TListBox *ListBox1;

  TPanel *Panel1;

  TBitBtn *BitBtn1;

private:

public:

  __fastcall THierarchyDlg(TComponent* Owner);

};



extern THierarchyDlg *HierarchyDlg;



#endif



Listing 23.15. HierarchyDlg has no custom code in it. The form simply provides a list box in which hierarchies can be shown.

///////////////////////////////////////

// HierarchyDlg.cpp

// 
Learning how to use objects

// Copyright (c) 1997 by Charlie Calvert

//

#include <vcl\vcl.h>

#pragma hdrstop

#include "HierarchyDlg1.h"

#pragma resource "*.dfm"

THierarchyDlg *HierarchyDlg;



__fastcall 
THierarchyDlg::THierarchyDlg(TComponent* Owner)

  : TForm(Owner)

{

}

This program creates five forms at application startup:

Application->CreateForm(__classid(TForm1), &Form1);


Application->CreateForm(__classid(TDMod), &DMod);

Application->CreateForm(__classid(TDataForm), &DataForm);

Application->CreateForm(__classid(TQueryForm), &QueryForm);

Application->CreateForm(__classid(THierarchyDlg), 
&HierarchyDlg);

Of these forms, the first two contain significant code, while the last three contain nothing but visual elements that can be shown to the user. For instance, the QueryForm provides a TDBGrid that can be filled with the results of SQL statements executed inside the program's data module. THierarchyDlg provides a list box that can be filled with the hierarchy of some of the objects used in the program.

The Warehouse application also relies heavily on the Widgets and MyObject modules. In fact, the majority of the program is really nothing but a set piece for the classes found in Widgets.cpp. These objects will be explained in the next section of this chapter.

The Hierarchy for the Widget and Pallet Components

Four components lie at the heart of the Warehouse program. These components are called TWidget, TPentium, TPentiumPro, and TDataPallet. All these objects are declared and implemented in Widgets.h and Widgets.cpp. The hierarchies for these components are shown in Figures 23.8 and 23.9.

FIGURE 23.8. The hierarchy for the Widget controls used to represent chips.

FIGURE 23.9. The hierarchy for the Pallet controls used to store chips.

As you can see, all these components descend from TCustomControl. The primary reason for choosing this ancestor was that it had a canvas. TCustomControl descendants can also contain other controls, which was an option I wanted to keep open in case I desired to build a widget that consisted of several sub-widgets.


NOTE: TGraphicControl also has a canvas, but it cannot contain subcontrols or receive the focus. In this case, I made a judgment call and decided that it would be wiser to use the more powerful TCustomControl rather than TGraphicControl, even though TCustomControl uses more resources and takes longer to paint. In the context of this book, it's not really too important which choice I made, so long as I communicate to you the relative virtues of each option.

In Figure 23.10, you can see the hierarchy for the Widget and Pallet controls taken together. For obvious reasons, there is more complexity in this dual hierarchy than there is in either of the single hierarchies shown in Figures 23.8 and 23.9. My point here is simply that the valuable thing about objects is their capability to isolate complexity. As a programmer, you want to find ways to break big, ungainly problems down into smaller, manageable programs. Objects are one of the best ways to achieve this goal.

FIGURE 23.10. The hierarchy for the Widget and Pallet controls, showing their mutual descent from TCustomControl.

When necessary, break off objects, or object hierarchies, into separate trees and study them alone. Write small test programs that explore the virtues and faults of one object in isolation. Make sure the code you write can be broken up into various smaller programs for testing. For instance, it is easy to take the TPentium component, drop it onto a form, and test it in isolation from the complexity found in the Warehouse program. Components make this kind of testing easy, and that is one of their greatest virtues: They are easily reusable.

Perhaps one of the hardest lessons that beginning programmers have to learn is the value of writing small test programs. If I can't break my programs down into smaller units that can be tested separately, I will generally concede that there is some flaw in my design.

To reiterate: One of the primary goals of OOP is to allow you to build discrete, reusable chunks of code that can be tested in isolation. Components aid in this process enormously, and it is one of the primary reasons why so many components are so robust. The key here is that components are easy to test, and, as a result, a lot of problems get caught that might otherwise be overlooked.

Understanding TWidget, TPentium, and TPentiumPro

The TWidget object provides a base object from which all widgets can descend. The TWidget class provides three basic properties common to all widgets:

__property Currency Cost;

__property AnsiString TimeCreated;

__property AnsiString Description;

As you saw in Chapter 20, "Encapsulation," widgets can also be saved to disk.

The TChip object is the base class for computer chips. In the implementation of this object I provide here, the TChip object has only minimal functionality:

class TChip: public TWidget

{

public:

  virtual __fastcall TChip(TComponent *AOwner): TWidget(AOwner) {}

  virtual __fastcall TChip(TComponent *AOwner, int ACol, int ARow)

   : TWidget(AOwner, ACol, 
ARow) {}

};

If you wanted to have more fun with this object, you could add a wide variety of fields:

class TChip: public TWidget

{

private:

  int MHZ;

  bool MMX;

  int Cache;

  int Transistors;

  
float Voltage;

  int RegisterSize; // 16, 32, 64?

public:

  virtual __fastcall TChip(TComponent *AOwner): TWidget(AOwner) {}

  virtual __fastcall TChip(TComponent *AOwner, int ACol, int ARow)

   : TWidget(AOwner, ACol, ARow) {}

};

Here you can see an object that contains standard fields for describing the basic attributes of a chip. I hope that in some future version of this program, I will add these features, but for now it is best if I push on and finish the book before my editors tell me what they are really thinking about the timeline for this book's development.

Descending from TChip are TPentium and TPentiumPro. As implemented here, the only thing unique about these two objects is their virtual Paint methods:

class TPentium: public TChip

{

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall TPentium(TComponent *AOwner): TChip(AOwner) {}

  virtual 
__fastcall TPentium(TComponent *AOwner, int ACol, int ARow)

    : TChip(AOwner, ACol, ARow) {}

};



class TPentiumPro: public TChip

{

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall TPentiumPro(TComponent 
*AOwner): TChip(AOwner) {}

  virtual __fastcall TPentiumPro(TComponent *AOwner, int ACol, int ARow)

    : TChip(AOwner, ACol, ARow) {}

};

void __fastcall TPentium::Paint()

{

  Canvas->Brush->Color = clPurple;

  Canvas->Rectangle(0, 0, 
ClientWidth, ClientHeight);

  Canvas->Brush->Color = clRed;

  Canvas->Ellipse(0, 0, ClientWidth, ClientHeight);

}



void __fastcall TPentiumPro::Paint()

{

  Canvas->Brush->Color = clGreen;

  Canvas->Rectangle(0, 0, 
ClientWidth, ClientHeight);

  Canvas->Brush->Color = clBlue;

  Canvas->Ellipse(0, 0, ClientWidth, ClientHeight);

}

In other words, my implementation doesn't do much to differentiate these objects other than to leverage polymorphism to make them appear differently when they are shown on-screen. A nice touch to add to this project would be to show a bitmap of a processor rather than the simple graphic I include here.

As you saw in earlier chapters, all TWidget descendants also have the capability to report on their hierarchy. This feature will play a role in this program when a user right-clicks on a component with the mouse. In particular, if you right-click on any of the Widget objects, a form that shows their hierarchy pops up. I will discuss that aspect of the program later in the chapter.

Because the TWidget components are descendants of TCustomControl, they all can be hung on the Component pallet. The following code from the Widgets unit registers the Widget and Pallet controls:

void __fastcall Register()

  {

    TComponentClass classes[5] = {__classid(TWidget),

       
__classid(TPentium), __classid(TPentiumPro),

       __classid(TPallet ), __classid(TDataPallet ) };

    RegisterComponents("Unleash", classes, 4);

 }

That's all that needs to be said about the technical aspect of the Widget components. When all is said and done, the most important fact about these simple objects is that they all descend from the same parent. As a result, the program will be able to use polymorphism when handling them. This turns out to be very useful, particularly during drag-and-drop operations. The drag-and-drop aspect of the program will be covered over the next few sections of this chapter.

Introducing the Pallet Controls

The TPallet controls are a little more complex than the Widget controls in that they can support drag and drop. In particular, they know how to respond when a user drops a TWidget control onto them.

Before describing the drag-and-drop operation, I should spend a moment covering the heritage of the pallet controls. The TCustomPallet control from which the TDataPallet descends is pretty straightforward:

class TCustomPallet : public TCustomControl

{

private:

  int FPalletNumber;

  TListHierarchy *FHierarchy;

protected:

  virtual void __fastcall Paint(void);

public:

  virtual __fastcall 
TCustomPallet(TComponent *AOwner);

  virtual __fastcall ~TCustomPallet(void)

    { delete FHierarchy; }

  TStringList *GetHierarchy() { return FHierarchy->GetHierarchy(this); }

  __property int PalletNumber={read=FPalletNumber, 
write=FPalletNumber};

};

This object has a field for storing the number of the pallet and, through aggregation, the capability to report on its hierarchy.


NOTE: One of my goals in these chapters is to convince some readers of the merits of aggregation. Multiple inheritance is a powerful tool, but it adds considerable complexity to your program. In the last few chapters, you have had several opportunities to look at aggregation. This technique takes a little more work to implement than multiple inheritance, but I believe it often ends up saving you time in the long run, because it is so much easier to understand and maintain than multiple inheritance.

My point here is not to criticize multiple inheritance, but rather to point out that aggregation is a valuable tool in its own right. There are certain settings in which each technology shines, and good programmers should explore the benefits of both aggregation and multiple inheritance so that they will know when to favor one method over the other.

When you are in doubt, I would suggest using aggregation, because it almost never causes trouble. Multiple inheritance is easier to implement, but under certain circumstances it can cause enormous confusion that leaves even the best programmers feeling frustrated. This is literally true. Some of the best programmers I have ever met have spent days, sometimes even weeks, trying to untie the knots caused by someone else's unintelligent or ill-advised use of multiple inheritance.

Of course, if you use multiple inheritance intelligently, it is a great tool. But one of the prerequisites for using it intelligently is knowing when not to use it. If you are not going to use it, you need to understand the alternatives. There is really only one good alternative to multiple inheritance, and that is aggregation. As a result, all programmers should understand both multiple inheritance and aggregation.

In Widgets.h, I declare a direct descendant of TCustomPallet called TPallet:

class TPallet : public TCustomPallet

{

public:

  virtual __fastcall TPallet(TComponent *AOwner): TCustomPallet(AOwner)

    { Width = 25; Height = 25; }

__published:

  __property PalletNumber;

  __property OnDragDrop;

  
__property OnDragOver;

  __property Color;

};

This object adds no functionality to TCustomPallet but only publishes certain of its properties. I place this object here for no other reason than to point out the correct way to design objects from which others may descend. If you want to be sure that you are following the best techniques, you might want to create two classes when it might at first appear that one will do:

1. The first class provides all the functionality needed by an object.

2.
The second class descends from the first and publishes the properties you think you will need to use for your particular version of the object.

When you do things this way, others can descend from the first class you created but get the option of deciding which classes they want to publish.

An example of this technology in action is shown by the TDataPallet:

class TDataPallet : 
public TCustomPallet

{

private:

  TTable *FWidgetsTable;

  TQuery *FWidgetsQuery;

protected:

  void __fastcall PalletDragDrop(TObject *Sender,

    TObject *Source, int X, int Y);

  void __fastcall PalletDragOver(TObject *Sender, TObject 
*Source,

    int X, int Y, TDragState State, bool &Accept);

  void virtual EnterWidgets(int Total, TWidget *W);

public:

  virtual __fastcall TDataPallet(TComponent *AOwner);

  void __fastcall QueryPallet(TQuery *Query);

  float __fastcall 
QueryPalletSum(TQuery *Query);

__published:

  __property TTable *WidgetsTable={read=FWidgetsTable, write=FWidgetsTable};

  __property TQuery *WidgetsQuery={read=FWidgetsQuery, write=FWidgetsQuery};

  __property PalletNumber;

  __property Hint;

  
__property Color;

  __property TabOrder;

  __property OnMouseDown;

  __property PopupMenu;

};

TDataPallet descends from TCustomPallet, thereby inheriting certain useful functionality. It then adds its own methods and publishes the properties that it wants to expose in the Object Inspector. The key point here is that TDataPallet should have the right to decide which properties it wants to expose!

The TDataPallet is smart enough to accept drag and drop, and also to work with objects of type TTable and TQuery. These are both relatively complex subjects, so I will handle them in their own sections of this chapter.

TDataPallet, and Drag and Drop

Drag and drop is a flashy interface element of the type that I have a natural tendency to resist. I just tend to think that the really hot technology involves the language itself, and particularly the construction of objects and components. However, there is a lot to be said for building programs that are easy to use, and drag and drop can aid in that process.

The one problem with drag and drop is that there are few simple ways to tell the user that the feature is available. When you open up the Warehouse program, there is no way to show users that they can drag components onto the pallets. You have to tell them this in the online help or in some kind of tutorial. The same problem exists in the Explorer, in many of the Zip utilities I've seen, and elsewhere in the application development world.

Lately, I've even seen some programs just write the words drag and drop in their captions. Even though the words appear out of context and with no specific reference, this is often enough to allow me to figure out how to use the application. I attempt this same thing with the Warehouse program, as shown in Figure 23.1.

After you get over the initial training period, drag and drop gets a chance to come into its own. It can then provide a very powerful addition to your programs, especially if used in the right context.

The VCL makes drag and drop simple to implement. To make it work, you need to respond to WM_MOUSEDOWN events on the component you want to drag:

void __fastcall TForm1::WidgetMouseDown(TObject *Sender, TMouseButton Button,

  TShiftState Shift, int X, int Y)

{

  if 
(!dynamic_cast<TWidget *>(Sender))

    return;

  TWidget *W = (TWidget *)(Sender);



  if (Shift.Contains(ssLeft))

  {

    W->BeginDrag(False);

  }

  else

  {

    HierarchyDlg->ListBox1->Items = W->GetHierarchy();

    
HierarchyDlg->ShowModal();

  }

}

The relevant code in this example is only three lines long. Stripped of the context of the particular example used in this program, it would look like this:

if 
(dynamic_cast<TWidget *>(Sender))

{

  TWidget *W = (TWidget *)(Sender);

  W->BeginDrag(False);

}

Checking to see that the component is really a Widget is probably overkill, because you would only associate this code with the OnMouseMove event of a control you wanted to drag. Nevertheless, it is often better to be safe than sorry. In particular, you want to know immediately if you accidentally associate the wrong code with the wrong component. In short, error checking is often as much about detecting programmer error as it is about detecting user error. If you decide later that you have a performance problem, you can strip this kind of error checking from your program, or else use assertions, conditional compilation, or some other technology.


NOTE: If you use the Object Inspector to examine TWidget or its descendants, you will notice that one of the two events it publishes is OnMouseDown. Obviously I published this event because I knew it would be needed. I also published the OnDragDrop event, but only because I think the user might need it under certain circumstances never exploited in this program.

After you have safely typecast the object as a Widget, you can begin the drag operation:

W->BeginDrag(False);

You should set the parameter BeginDrag to false if you want the cursor to change only after a drag operation is begun. For instance, if the user wants to click on a component for some other reason than dragging, you would want to set the parameter to false, so that the cursor will not change if users merely click on a component but only if they click and then start dragging.

Starting a drag-and-drop operation is obviously fairly trivial. The other side of the operation is a bit trickier but still not rocket science:

void __fastcall TDataPallet::PalletDragOver(TObject *Sender,

  TObject *Source, int X, int Y, TDragState State, bool &Accept)

{

  Accept = dynamic_cast<TWidget 
*>(Source);

}

void __fastcall TDataPallet::PalletDragDrop(TObject *Sender,

  TObject *Source, int X, int Y)

{

  if (WidgetsTable == NULL)

    ShowMessage("No table assigned to the WidgetsTable property");

  else

  {

    if 
(dynamic_cast<TWidget *>(Source))

    {

      TWidget *W = (TWidget *)(Source);

      AnsiString S = "Enter number of " + W->Name;

      AnsiString NumWidgets;



      if (InputQuery("Widget Number Dialog", S, 
NumWidgets))

      {

        EnterWidgets(NumWidgets.ToInt(), W);

      }

    }

  }

}

The first of the two methods shown here is an event handler that is called when the user drags something over the component. If you set the Accept parameter to an OnDragOver event to true, the cursor on the mouse will change to reflect that this object accepts the current type of drag-and-drop operation. The code shown here sets Accept to true as long as I am sure the object in question is a Widget:

Accept = dynamic_cast<TWidget *>(Source);

This is where polymorphism plays such a key role in the program. The issue here is that polymorphism allows me to safely typecast any object that descends TWidget as a Widget. This is an enormously powerful concept. By now you are used to the idea that TWidget, TPentium, and TPentiumPro are closely related objects. Nevertheless, they are not the same object, and if I had to write code that worked with three different types of objects, my application would be much more difficult to write and maintain.


NOTE: Polymorphism allows you to treat objects generically, as a type. I can talk not about a specific widget, but about all widgets, regardless of their subtype.

When dealing with real-world objects, the same thing is done all the time. For instance, I explain how to do something to you, the reader of this book, without having to know your name, your race, your sex, or your age. I can just refer to readers in general and know that you will be able to understand the explanation, given the context of the expected audience for this book. If I had to address each copy of this book to a specific person, it would be considerably less useful.

Polymorphism lets the programmer tell a whole class of objects what needs to be done. This book works with all programmers who understand C++ and the basics of OOP. The methods shown here work with all objects that descend from TWidget. This capability to generalize, to work with abstractions, is enormously powerful!

When the user actually drops a component on another component, an OnDragDrop event occurs:

void 
__fastcall TDataPallet::PalletDragDrop(TObject *Sender,  TObject *Source,

                                           int X, int Y)

{

  if (dynamic_cast<TWidget *>(Source))

  {

    TWidget *W = (TWidget *)(Source);

The code shown here is a stripped-down version of the response to this event. The component that was dropped is stored in the Source parameter of the OnDragDrop event. I can simply typecast it as a TWidget and then begin calling its methods. Some of the actual methods called, such as EnterWidgets, are actually more related to the database part of this equation, so I will explain them in the next section of this chapter.

The final point to notice about these drag-and-drop operations is that the event handlers shown here are not part of the main form of the application but are instead methods of the TDataPallet itself! In particular, notice the constructor for the object:

__fastcall TDataPallet::TDataPallet(TComponent *AOwner)

: TCustomPallet(AOwner)

{

  OnDragOver = PalletDragOver;

  OnDragDrop = PalletDragDrop;

}

This code sets up event handlers for OnDragDrop and OnDragOver events. In other words, when you drop a TDataPallet onto a form, it is automatically ready to handle drag-and-drop events without any custom coding from the user.

To go any further on this subject would be to start wrestling with the tables used in this project. My plan is to cover that material in the next section of the chapter.

TDataPallet and Databases

In an ideal world, TDataPallet could store itself directly in an object-oriented database. BCB is, in fact, capable of using OOP databases such as Poet, but that is not a feature I want to stress in this book, because few readers will be using such tools.

Instead of using an object-oriented database, I instead use the properties of TDataPallet to allow the user to hook in TTable and TQuery objects. That way TDataPallet can have "carnal knowledge," as it were, of the databases in your application.


NOTE: The relationship between TDataPallet and the TTable objects used in this program is very specific. You can't use just any TTable object with TDataPallet but only the one that contains a particular set of fields.

In many ways, this tight coupling of a particular TTable and the TDataPallet object is less than ideal in terms of OOP design. The problem here is that TDataPallet needs to know too much about the structure of the TTable object you are using with it. There is not a sufficient degree of abstraction here to suit the best principles of OOP design.

The origin of the problem is simply that I can't store TDataPallets and TWidgets directly in an OOP database, nor can I easily descend visible controls from the non-visible TTable object. As a result, I need to come up with a compromise solution to a problem that a later generation of tools will undoubtedly solve for me.

For now, I can come up with a reasonable solution simply by creating a TDataPallet component with a simple, easy-to-use interface. This simple interface makes it easy for the user to see what needs to be done to use the object.

Each TDataPallet has a TWidgetTable property:

private:

  TTable *FWidgetsTable;

  TQuery *FWidgetsQuery;

__published:

  __property TTable *WidgetsTable={read=FWidgetsTable, write=FWidgetsTable};

  __property TQuery *WidgetsQuery={read=FWidgetsQuery, write=FWidgetsQuery};


These properties can be manipulated via the Object Inspector, as shown in Figure 23.11, which makes it easy for the user to connect the TDataWidget to the appropriate table. Learning to make components interact in this way is a key skill in BCB programming.

FIGURE 23.11. Using the Object Inspector to connect a TDataPallet to a table.

After the table is connected to the data pallet, the consumer of the object need not think about it any further. For instance, when you drag a widget onto a pallet, the method TDataWidget is called in order to enter data into the table:

void TDataPallet::EnterWidgets(int Total, TWidget* W)

{

  int i;

  for (i = 0; i < Total; i++)

  {

    FWidgetsTable->Insert();

    FWidgetsTable->FieldByName("Name")->AsString = W->Name;

    
FWidgetsTable->FieldByName("Created")->AsString = W->TimeCreated;

    FWidgetsTable->FieldByName("Description")->AsString = W->Description;

    FWidgetsTable->FieldByName("Cost")->AsCurrency = 
W->Cost;

    FWidgetsTable->FieldByName("PalletNumber")->AsInteger = PalletNumber;

    FWidgetsTable->Post();

  }

}

In short, after you hook up the TDataPallet object to the table, the pallet knows how to manage the table. This helps to keep the code for your program clean, by isolating each problem inside of discrete objects.

From the point of view of the consumer of the TDataPallet, all he or she has to do is drop a TDataPallet on a form and then hook it up to a TTable object. After that, all the database and drag-and-drop operations are taken care of automatically! Obviously, it would be better if there were a more pure OOP way to relate the TTable and TDataPallet objects, but given the limitations of the current technology, the VCL provides a means to implement a very clean, simple solution to the problem. This is what OOP is all about: finding simple, error-free, easy-to-understand solutions to problems that would otherwise be complex and error-prone.

The last point to make about this solution is that it allows the user to simplify the act of data entry. If someone had to enter each of these objects in a database with the keyboard, it would be a very boring, time-consuming task. The Warehouse program completely eliminates the need for any painful data entry other than typing in the single digit specifying the number of components to drop on a pallet.

This kind of solution could be implemented in a wide range of situations. For instance, with a hospital, someone could enter the basic facts about a patient once and then simply drag that patient's icon onto a form in order to transfer the information from the original record to the form. Even better, a fully digital world could equip each patient with a card that would be read on admission to the hospital, so that the basic data on the patient would be imported into the database automatically. Then you could just drag and drop a picture of the patient onto any forms needed by insurance companies or other entities that live on raw data. This type of solution is probably not very widely used at this time, but it seems obvious that it will be over the course of the next few years.

Now that most companies are computerized, the way to be competitive is to provide intuitive, easy-to-use solutions that allow individuals to get a lot of paperwork done quickly. If someone provides clunky, hard-to-use solutions that users dislike and that require a lengthy training period, others can get a competitive advantage by coming in and providing clean, easy-to-use interfaces that workers enjoy and that require only a few minutes of training.

Querying the Database

After you have the drag-and-drop solution to data entry in place, the final step is to use SQL to query the database. I have divided up these chores between two different objects: TDataPallet and the TDataModule. If a question applies directly to a single TDataPallet, I ask the data pallet itself to reply. If the question applies generically to the entire warehouse, I ask the program's data module to answer the question.

Generic questions that apply to the entire warehouse are accessed through the menu at the top of the program, while questions that apply to a particular pallet are accessed through a local menu found by right-clicking on the object, as shown in Figure 23.12.

FIGURE 23.12. Right-clicking on a pallet to find its local menu.

To associate an object with a popup menu, first drag the menu from the Standard page of the Component Palette. Then use the Menu Designer to fill out the menu. Finally, use the PopupMenu property of the component with which you want to associate the menu. For instance, I use the PopupMenu property of TDataPallet to associate the program's popup menu with each pallet. You can multiselect all the TDataPallets on the form and then change their PopupMenu properties globally with one click of the mouse. TDataPallet inherited its PopupMenu property from TCustomControl.

This is probably not the place to get into a long explanation of the SQL that answers the user's questions about the database. However, the following examples from the program's data module provide a sample of how this system works:

void __fastcall TDMod::SumByProduct()

{

  AnsiString S = 
"Select Name, Count(*), Sum(Cost) "

                 "from Widgets "

                 "group by Name;";

  WidgetsQuery->SQL->Clear();

  WidgetsQuery->SQL->Add(S);

  WidgetsQuery->Open();

}


This code provides a report on all the widgets in the database. As you can see, it groups them by name, as shown in Figure 23.4. The query reports on the number of each particular widget (Count(*)) and on the total value of the stock of each particular widget (Sum(Cost)).

The following example shows how to report on the contents of each pallet in the warehouse, as shown in Figure 23.5:

void __fastcall TDMod::ReportByPallet()


{

  AnsiString S = "Select PalletNumber, Name, Count(*), Sum(Cost) "

                 "from Widgets "

                 "group by PalletNumber, Name;";

  WidgetsQuery->SQL->Clear();

  
WidgetsQuery->SQL->Add(S);

  WidgetsQuery->Open();

}

As you can see, this query groups the pallets by number and name and reports on the total number of items on the pallet and the total value of the items on the pallet. SQL provides an elegant, simple means of retrieving this data that is vastly superior to trying to retrieve it through a series of loops written in C++.

I don't want to dwell on this subject too much longer, but I will show one method of the TPallet object answering a question from the database:

float __fastcall TDataPallet::QueryPalletSum(TQuery *Query)

{

  AnsiString S = "Select Sum(Cost) from Widgets where PalletNumber = " +

    
AnsiString(PalletNumber);

  Query->SQL->Clear();

  Query->SQL->Add(S);

  Query->Open();

  return Query->Fields[0]->AsFloat;

}

This code returns the value of the items on a particular pallet. You can show the answer to the user either in a custom-made dialog that works with the return value of the function or by showing the results of the query in a TDBGrid.

The user would ask the question answered by the QueryPalletSum method by right-clicking on a particular pallet and bringing up the local popup menu for the pallet. The response method for selecting an item on the menu looks like this:

void __fastcall TForm1::ValueofItems1Click(TObject 
*Sender)

{

  TDataPallet *D = (TDataPallet *)PopupMenu1->PopupComponent;

  D->QueryPalletSum(DMod->WidgetsQuery);

  QueryForm->Panel1->Caption =

    "Value of Items on Pallet: " + AnsiString(D->PalletNumber);

  
QueryForm->ShowModal();

}

This menu item response method can find the pallet the user clicked on by checking PopupComponent property of the PopupMenu itself:

TDataPallet *D = 
(TDataPallet *)PopupMenu1->PopupComponent;

After you have the pallet in your hands, you are free to call its methods, such as the QueryPalletSum method shown earlier.


NOTE: In this case, I probably could skip the step of asking the user to pass the query explicitly to the function, because you can already associate the TQuery object with the TDataPallet object via the Object Inspector. However, I used this technique because it seemed expedient at the time and perhaps allowed me to write code that was easier to read.

After the TDataPallet returns the answer to the query, I use the QueryForm object to display the reply to the user. The QueryForm is a standard VCL form with a TDBGrid on it. The advantage of this system is that I can just pass a query to the QueryForm, and it will display the results to the user, regardless of the type of question being asked.

Looking at Hierarchies

The final thing to point out about this program is that you can right-click on the TWidget and TPallet objects in order to see their hierarchies, as shown in Figures 23.13 and 23.14.

FIGURE 23.13. The hierarchy of a TWidget descendant as found by right-clicking on the object.

FIGURE 23.14. The hierarchy of a TCustomPallet descendant found by right-clicking on a pallet and choosing from a popup menu.

I've come back to the subject of hierarchies at the end of this chapter just for the sake of completeness. As you recall, the discussion of objects was started back in Chapter 19, "Inheritance," by implementing a simple base object that could show its own hierarchy. You've seen a number of sample programs that use that functionality in a variety of different contexts, including this relatively sophisticated program that manages to embody the traits of objects you first started exploring several chapters ago.

The real point of the Warehouse example is not so much to simulate a warehouse as it is to show how objects work. The fact that each of the key objects in the program allows you to see its hierarchy reminds you of the purpose of this program and of its real import.

Summary

One of the goals of OOP is to allow you to create objects that encapsulate functionality in a reusable container that makes applications easy to maintain. To achieve these ends, you use inheritance, encapsulation, and polymorphism in a variety of different contexts. The object hierarchy is, in a sense, a symbol of this power.

In this chapter, you took a look at working examples of object hierarchies, polymorphism, inheritance, and encapsulation. You have seen how to wrap objects inside components and use them in a relatively real-world example.

You have also seen how to use drag and drop and right mouse clicks to provide the user with a simple, easy-to-understand interface to an application. Both drag and drop and right clicks are confusing to the user at first, because their availability is not always clearly broadcast by symbols visible on the interface of the application. However, after the user is clued in to their existence, they add a great deal to the interface of a program. As time goes on, right-clicking and drag and drop will become standard parts of all programs, and users will then be more inclined to search out these features and use them, without having to be specifically alerted to their existence.

TOCBACKFORWARD

©Copyright, Macmillan Computer Publishing. All rights reserved.