| 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.
|