Advanced Concepts in Data Module Usage

   

After you get beyond using basic data modules, there are a number of advanced design and development techniques that are available to you. These include the following:

  • Using specialized objects and event handlers to encapsulate application logic.

  • Keeping data modules and user interfaces separate with data awareness and interface objects.

  • Using form inheritance to create trees of related data modules.

  • Dealing with special issues of making sure linked components are found by other data modules and forms when they are using a data module that is a form-inheritance descendant, and when they refer to components in the ancestor .

  • Using data modules in packages.

Form Inheritance with Data Modules

Form inheritance works with data modules as it does with regular forms. Simply add a data module to the repository, and you can then use File, New to use the data module in a project (Use), create a copy in the current project (Copy), or create a descendant (Inherit) of the data module. Figure 7.4 shows the File, New dialog.

Figure 7.4. File, New inheriting from a data module previously added to the repository.

graphics/07fig04.gif

Of the three ways to use data modules from the repository, Inherit is by far the most useful. With it, you can extend the capabilities of a data module by adding components, adding fields, adding or extending event handlers, or changing properties. (Note that you cannot remove a component derived from the ancestor.)

Data Module form inheritance can be used for several different purposes:

  • Avoid commitment to a specific set of data set components by creating a base data module with only data sources. Link forms to the data sources in the base data module. Then, add actual data sets to the descendant. You can use this technique to create a data module that supports both ADO and BDE components. Note that you will need to create event handling member functions in the base class that can be called by the descendants to implement the event handling. The descendant data module actual event handlers can then call these functions.

  • Add capabilities to a data module to support a variety of product lines. The core product can be supported with the base data module, and the descendants can support capabilities added to that core product, including additional tables, fields, and processing.

  • Create lookup and editing descendants of the base data module. The editing descendant can offer event handlers for validation of persistent fields and to perform actions when data is changed. The lookup descendant can have data sets with the same names to satisfy lookup fields and Locate() operations.

Handling Uneven Form Inheritance with Data Modules

When using form inheritance with data modules, there are several important elements to keep in mind. First, if you intend to use a descendant data module with a base class form that refers to the data module ancestor, you need to make it possible for the runtime component lookup system to find the descendant when it is actually looking for the base data module. This can be handled by code similar to Listing 7.2, where the base classes are Base1 and Base2 and the descendant classes are Descendant1 and Descendant2 .

Listing 7.2 Helping C++Builder Find Inherited Data Modules at Runtime
 TFindGlobalComponent OldFindGlobalComponent;  TComponent* __fastcall FindGlobalComponentExtended     (const System::AnsiString Name)  {      if (Name == "Base1") return (TComponent *) (Base1 *) Descendant1;      if (Name == "Base2") return (TComponent *) (Base2 *) Descendant2;      return OldFindGlobalComponent(Name);  }  WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)  {      try      {          Application->Initialize();          OldFindGlobalComponent = FindGlobalComponent;          FindGlobalComponent = FindGlobalComponentExtended; 

In addition, each data module's constructor (base or descendant) must set the data module global variable because the automatic initialization of that variable will not occur in the ancestor. For instance, in Base1

 Base1 = this; 

and in Descendant1

 Descendant1 = this; 

How to Avoid Dependence on Specific User Interfaces

There are two ways to create data modules that avoid dependencies on specific user interfaces. One, the most common and the safest, is to use data-aware controls for that purpose. The second, which should only be used very carefully , involves making available a variable, function, or property in the data module class definition that the user interface can use to register a control with the data module. To safely implement the latter case, the constructor should set that reference to NULL , and every reference to the control should be tested to verify that the pointer is not NULL . This will enable the data module to work properly, even when there is no user interface, or when the user interface does not contain a desired control.

How to Work with Application-Specific and Framework Components in Data Modules

One important design strategy for working with data modules has to do with creating and using specialized generic and application-oriented components and their event handlers to encapsulate application logic.

Most developers simply use the generic components that are part of the VCL or third-party libraries. Event handlers, such as AfterPost() , are then used to enforce application logic, such as forcing the refresh of related data sets when a new record has been written.

But you can also create both generic and application-specific components with their own event handlers and use those components to express complex application logic in the context of a data module. For example, the generic nonvisual component interface in Listing 7.3 offers a framework for undoable actions.

Listing 7.3 An Application-Oriented, Generic, Nonvisual Component Interface
 class TUndoableFramework: public TComponent  {      public:          void __fastcall (__closure *)           TExecuteHandler              (TUndoableFramework *theUndoableFramework);        void __fastcall (__closure *)           TRecordUndoHandler              (TUndoableFramework *theUndoableFramework);        void __fastcall (__closure *)           TUndoHandler              (TUndoableFramework *theUndoableFramework);        void __fastcall (__closure *)           TCleanupUndoHandler              (TUndoableFramework *theUndoableFramework);      private:          TExecuteHandler myToExecute;          TRecordUndoHandler myToRecordUndo;          TUndoHandler myToUndo;          TCleanupUndoHandler myToCleanupUndo;      protected:        virtual void __fastcall DoExecute(void);        virtual void __fastcall DoRecordUndo(void);        virtual void __fastcall DoUndo(void);        virtual void __fastcall DoCleanupUndo(void);        public:           virtual void __fastcall TUndoableFramework(void);           virtual void __fastcall ~TUndoableFramework(void);           virtual void __fastcall Execute(void);              // also invokes ToRecordUndo           virtual void __fastcall Undo(void);           virtual void __fastcall CleanupUndo(void);        __published:           __property TRecordUndoHandler ToRecordUndo =              {read=myToRecordUndo,write=myToRecordUndo};           __property TUndoHandler ToUndo =              {read=myToUndo,write=myToUndo};           __property TCleanupUndoHandler ToCleanupUndo =              {read=myToCleanupUndo,write=myToCleanupUndo};  }; 

This shows a component that provides event handlers for performing an action (which invokes an additional handler to record the undo information as a side effect); for undoing that action; and for cleaning up any special remnants of preparation for undo. For example, if undoing a delete actually involved changing the marking on deleted records rather than deleting them, undo cleanup might include performing a final, nonundoable delete of those records. It does not specify how to perform those actions (which are managed by the handlers), only the available actions and, in one case, constrains the order in which they can be performed.

Used in a data module, this component makes it easier to reflect the structure of a system that involves undo. Descendants of the component class can offer specific methods wrapping the calls to the handlers for executing, recording undo, undoing, or cleaning up, or form inheritance can be used with the data module and handlers can be augmented in the data module descendant. A combination of the two techniques can be used ”for example, a descendant can offer an event stack for recording events, while descendants of the data module can augment the execution handler to cope with additional requirements.

This is a generic nonvisual component for data module use, but you can also create an application-specific nonvisual component for data module use. For example, the interface to a component (shown in Listing 7.4) handles reservations . (Note that the ... indicates parts of the code not shown to keep the example brief.)

Listing 7.4 A Completely Application-Specific Nonvisual Component
 class TReservationDesk: public TComponent  {          public:        void __fastcall MakeReservation           (TReservation *theReservation);        void __fastcall UpdateReservation           (TReservation *theReservation);        void __fastcall RemoveReservation           (TReservation *theReservation);     __published:        __property TQuery *Reservations =           {read=myReservations,write=myReservations};        __property TAfterReservationAdded =           {read=myAfterReservationAdded,write=myAfterReservationAdded};        __property TBeforeReservationUpdated BeforeReservationUpdated        __property TAfterReservationUpdated        __property TBeforeReservationRemoved        __property TAfterReservationRemoved  }; 

This component offers the capability to use separate instances of the component for different reservation tables (probably assuming a common field naming scheme), and allows the developer to customize the component behavior with specific event handlers keyed to the available member functions. Of course, such a component can also be further specialized through its own inheritance tree, or its instances can be specialized through the use of form inheritance.

Data Modules in Packages

Data modules, like forms, can be used in packages. However, a common problem arises when you use the package modifier directly in the form class definition ”in that case, the data module suddenly looks like a regular form, and all of the data module designer features disappear. This can be easily solved using the following forward and actual definition:

 class PACKAGE TMyDataModule;  class TMyDataModule 

   
Top


C++ Builder Developers Guide
C++Builder 5 Developers Guide
ISBN: 0672319721
EAN: 2147483647
Year: 2002
Pages: 253

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net