HOME, PRODUCTS, ARTICLESCONTACT

BUY UPC™UPC™ FEEDBACKUPC™ SUPPORT

C.03. HOW TO BUILD SUPERIOR PAGED, MULTIPLE WORKSPACE (PMW) APPLICATIONS

This topic provides guidelines for developing software with the Paged, Multiple Workspace (PMW) model. Why PMW? How can we use PMW, and how should we use PMW? What is the appropriate design and coding approach? This topic attempts to answer these questions, and provides source examples for optimal implementations.

WHAT IS PMW? WHY PMW?

Paged, Multiple Workspace is an application development pattern similar to MDI, but which hosts separate workspaces in pages within a single main application window. Many advantages derive from this model, the nuclei of which are page controls.

While MDI was formerly used only for applications which were obligated to host multiple workspaces, the PMW model better serves all the purposes of MDI, and further provides an ideal model for promoting the usual Single Workspace (SW) application into a processor of unlimited workspaces.

Typical SW applications can actually be written with smaller code bases in UPC™ PMW, because UPC™ provides usual interface features such as paged toolbars, and because your project does not have to furnish code bases to drive functions such as wrapping or spatial maintenance of interacting regions. The UPC™ PMW pattern strips the usual development duties of SW projects to writing their usual processing core, which is automatically focused on managed workspaces by the UPC™ PMW infrastructure. Both MDI and SW therefore can be written on substantially smaller code bases, while advanced PMW better negotiates the workspaces of MDI, and multiplies the usual SW implementation into a substantially more valuable product, capable of managing unlimited workspaces from the core code base of the former SW implementation.

These advantages make PMW a most promising candidate for practically every GUI application.

PROGRAMMING ADVANTAGES

PMW's simple purposes are accomplished by a marriage of two things:

  • An array based workspace organization by which a processing core can be applied to unlimited workspaces (pages).
  • A prebuilt infrastructure which provides propagation, negotiation, and management of the workspaces.

With a prebuilt infrastructure comprising all but the unique elements of the eventual application, the task of development is largely reduced to wiring your processes to instances of the Pages[] array (TList). Instead of operating on one object of a given kind, your processing core operates on member objects or data of a page. As a result,

  • A conventional application's processing core can process unlimited instances of work.
  • Workspaces are focused automatically by the prebuilt infrastructure.
  • A relatively small code base (the processing core) readily drives potentially huge applications, consuming minimal resources.
  • Whole areas of development are eliminated, and the small core that remain are reduced to the fewest possible issues.

Members of Pages[] become our operands, with page modules furnishing the entire necessary infrastructure for propagating, negotiating, managing, and focusing the usual processing core on unlimited instances of work. A simple Single Workspace application is transformed into a PMW implementation by placing a page module onto a form and re-wiring its operations to members of pages. Multiple Document Interface applications are transformed by throwing away your child window creation and destruction cycles and your navigation system and moving your interface controls to pages.

With whole areas of development eliminated, and with the remaining task reduced to wiring a minimal processing core to Pages[], PMW produces substantially faster development cycles, and exceedingly efficient product.

OPTIMAL DESIGN

PMW is so resilient as to tolerate however you may want to work with it. But there is always such a thing as optimal design, and a purpose of UPC™ is to make optimal design easier and faster to achieve than any other approach, even for neophytes. The techniques outlined by this article are easily accomplished, and will eliminate much unnecessary complexity from your work.

The trail we are about to blaze benefits from subclassing. The little time spent mastering this approach will cut weeks or months from many development cycles, as the patterns and code provided allow anyone to deploy optimal designs in minutes.

The greatest advantages of the Paged, Multiple Workspace architecture are important to understand, because they are realized by deriving custom page classes. The purpose of deriving page classes is to make our operands formal members of our pages. The purpose of the few lines of code which formally bind member data and objects to our pages is to use the prebuilt functionality of our page module to maintain all of our processed members for us. As the prebuilt architecture manages its integral Pages[] arrays, it propagates, re-orders, and destroys our member data together with the pages.

Thus we all want to derive custom page classes wherever this can serve us, because subclassing eliminates the substantial work of writing a redundant second infrastructure (as we would in a conventional application). By subclassing, single algorithms perform universal operations at one juncture of your application. Otherwise, many individually coded algorithms are required to parallel the processes of the prebuilt infrastructure which can do all this for you.

We will understand much more clearly how much work and complexity we eliminate, and how much more efficiently a minimal resource footprint will do the same work for us, after we address how to apply your processing core to formal or informal members of your pages.

WHETHER WE WILL BENEFIT FROM SUBCLASSING

First, let's address the ostensible question of whether we will benefit from subclassing, which depends on just two things:

  • Whether we have data, objects or methods which we process in any way that can benefit from integrating them with our pages; and, if we do:
    • Whether the overhead of subclassing is less than processing informal members otherwise.

If our pages have any common members, subclassing will always produce the best structure and the opportunity to write the least, most efficient code. Because subclassing is so little work, and because alternate approaches multiply that work, the odds of benefitting extensively are tipped steeply in favor of deriving custom page classes. Altogether, if you have "data" (constants, variables, objects, or methods) which can be made formal members of your pages, you will almost certainly benefit substantially by subclassing pages.

BASIC OVERHEAD OF SUBCLASSING UPC™ PAGES OR MODULES

What is the basic development overhead and technical challenge (if any) of subclassing?

UPC™ page modules are designed to reduce deployment of custom page classes to an extremely simple matter. UPC™ PMInterfaces will even propagate and manage unlimited page classes at run time or design time. All the developer has to do is inform the PMInterface what page class the developer wants the PMInterface to propagate. Everything else is automated:

  • To configure a PMInterface to propagate a page class, you only have to assign the page class identifier to the PMInterface's PageCreationClass field.
    • Any combination or succession of propagation method calls will then propagate instances of PageCreationClass.
    • Related methods will manage the instances of the class without any further attendance.
    • Design time editors of the original module will also propagate PageCreationClass. Thus, if you want or need to subclass a UPC™ module to propagate a custom page class at design time, you don't have to do anything but assign the class identifier to Module.PMInterface.PageCreationClass. No complex issues exist. A single line of code accomplishes this purpose.

TWO CASES OF SUBCLASSING

Design time or run time page propagation needs establish two different cases for which we subclass. Complete source for both cases follows:

  1. If our application only needs to propagate pages at run time:
    • We only have to derive our custom page class.

      Because our module only needs to propagate our custom page class at run time, we can assign our page class identifier to UPC™'s PageCreationClass field in the OnCreate event of our form, and we will even be able to work with the (unpopulated) module straight-away in the development environment, because our propagation occurs at run time.

  2. If our application needs to propagate custom page classes at design time:
    • We must also derive and install a module that will produce our custom page class when the module is created in the IDE.

      We produce such a module by overriding its constructor and writing a single line of code which assigns our page class identifier to the PageCreationClass field. As a mere few clicks perform the install, both the page and module descendants can be written and installed in a few minutes.

Either subclassing case therefore is regularly accomplished by a small, simple body of code which most efficiently accomplishes our purposes. The PageCreationClass field is key to this simplicity, because internalization automates all related management. There are no issues but adding what formal members you want to belong to your pages and assigning class identifiers to PageCreationClass before propagating.

Before we move to these topics however, we first address how to apply our processing core to members of our pages, because this illustrates how much work we actually eliminate by subclassing. Whether we have to generate custom page classes or not, the principles of applying our processing core to members of our pages are key to developing far more powerful, efficient applications with far less overhead, and for determining the best and most easily delivered application designs for any type of implementation.

C.04.1. APPLYING YOUR PROCESSING CORE TO MEMBERS OF THE FOCUSED PAGE

WHY IT IS PREFERABLE TO SUBCLASS

We first need to explain the differences between integrating formal members with our pages, or maintaining parallel arrays of informal members.

In this explanation, "focused page" may apply to a Pages[] member iterated by a process, but the primary meaning of "focused page" will be a page instance focused by the UPC™ infrastructure. As before, "data" may mean constants, variables, objects, or methods. "Members" will be the related data of pages. "Formal" members are formally bound to the page by subclassing. "Informal" members are (necessarily) bound to the page by manually coding the relationship at every necessary juncture.

In soundly processing an object of the focused page, a process first verifies that there is a focused page (and/or that the focused page belongs to the intended class). After this routine test, your processing core simply applies its normal operations to formal or informal members of a page. Instead of opening a file and assigning it to a single file variable of the application for instance, the file is opened and assigned to the file variable of the focused page (a formal member), or to an array of file variables kept in parallel to the array of pages (informal members).

The latter approach is less preferable, because it requires coding that synchronizes the parallel arrays with the Pages[] array. If a page is inserted at a certain position, elements must be inserted into all of your parallel arrays in the same position. If a page is moved to a new position, all of its elements in the parallel arrays must be moved to the same position. If a page is deleted, all of its elements in the parallel arrays must be deleted in such a way that the resultant condition of the parallel arrays reflects the resultant order, focus, and elements of the remaining, associated pages.

While this is not preclusively challenging, subclassing pages is much easier, less confusing, and more efficient.

If you build formal members into your pages by subclassing, your UPC™ module inherently manages the related data for you. If a page is inserted, appended, moved, focused, defocused, or deleted, formal members and even focus simply go along for the ride.

Moreover, you can address formal members in the preferable manners of PageName.Member, Pages[Index].Member, or MyPageModule.MyPMInterface.FocusedPage.Member. To our greatest possible benefit, formal membership means the very Pages[] array (TList) is the sole vehicle of order and existence — which is wholly managed for us by the PMInterface. Consequently, we can simply iterate and/or focus members of this array to do any of the principal work of our application. There is no need to manually simulate formal binding at all.

These are the reasons that optimal PMW designs will involve subclassed pages wherever data can be formally bound to pages. By this simple organization, the same processing core we would deploy in a Single Workspace (SW) implementation can (without any further coding) process unlimited workspaces.

PREFERABLE TECHNIQUE: PROCESSING FORMAL MEMBERS OF THE FOCUSED PAGE

UPC™ provides a FocusedPageIsAssignedAndNotNil function to test whether we can process an ostensible focused page. The following Delphi and C++ examples show how to routinely test for process worthy conditions before processing formal members of your focused page. Note that one instance of any such method processes unlimited pages, and that the very navigation system of your UPC™ module even focuses your processes on the intended workspace:

DELPHI EXAMPLE

Copy code to clipboard (JavaScript/IE).

C++ EXAMPLE

Copy code to clipboard (JavaScript/IE).

Simple.

MORE OVERHEAD-INTENSIVE TECHNIQUE: PROCESSING INFORMAL MEMBERS OF ARRAYS OR STATIC, INFORMAL COLLECTIONS

To accomplish the same purpose without binding formal members to our pages, alternatively we can,

  1. Declare pointer variables.
  2. Apply our processes to these pointers.
  3. And automatically focus the processing core by assigning the informal members of the focused page to these pointer variables in the OnFocus event of the page or module.

While this may appear to be relatively efficient, note that in addition to our regular process, we must write an additional OnFocus handler or fork for every page, as well as additional assignments for every simulated member relationship. These multiply our processing footprint and work by PageCount times informal members:

DELPHI EXAMPLE

Copy code to clipboard (JavaScript/IE).

C++ EXAMPLE

Copy code to clipboard (JavaScript/IE).

Subclassing eliminates the need to write this redundant parallel infrastructure.

C.04.2. SUBCLASSING TADVANCE_Page

DETERMINING WHICH TADVANCE_PAGE CLASS TO DESCEND FROM

TWinControl

TADVANCE_Page

TADVANCE_Page_MainComposite

TADVANCE_Page_MainDataMaster

TADVANCE_Page_PagedTools

TADVANCE_Page_RelatedAndStatus

The first step of deriving a page descendant with formal members is to determine which page class to descend from.

You determine the page class you must descend from by identifying the PMInterface class which has to manage your page population. Note that the type of every PMInterface is indicated in the Object Inspector — making it a simple matter to determine your page base class from the PMInterface ClassName and the class tree above. You can drop a module onto a form, select the PMInterface you are going to build custom pages for, and read its class right in the Object Inspector.

Having identified the class of your PMInterface, you refer to the class tree above. Page ClassNames refer to the PMInterfaces which manage them. TADVANCE_Page_MainComposite is a multi-purpose class deployed for the main pages of the composite UPC™ modules except the DataMaster. DataMaster modules deploy TADVANCE_Page_MainDataMaster for their main pages.

  • Your derived class must descend from the right-most TADVANCE_Page descendant which matches your PMInterface by this nomenclature.

    Because our first examples subclass pages for a Status PMInterface, these examples descend from TADVANCE_Page_RelatedAndStatus. (A working copy of this source is implemented in the Demo applications — the full Delphi and C++Builder source of which is furnished with your UPC™ package.)

CLASS DEFINITION

Now we will write the few lines of code which define our page class and formally bind its members. Class definitions must be placed outside of any other class definition. Because we are not installing our page class in the IDE, our definitions can be placed in the unit of our form. The usual rules apply:

  • YOUR CLASS DEFINITION

    Class definitions declare the base class and formal members of the class.

    • Delphi™ class definitions must be placed in the interface section of a unit (.pas).
    • C++ class definitions are placed in the header (.h*).
  • METHOD DEFINITIONS

    Method "definitions" (as opposed to "declarations") are the operative code (implementation) of the method.

    • Delphi™ method definitions are placed in the implementation of a unit (.pas).
    • C++ method definitions are placed in the source file (.cpp).
  • CONSTRUCTORS, DESTRUCTORS, AND INITIALIZATION VALUES
    • You will regularly bind objects to your page class. If objects are made formal members, the page constructor must create the objects and the page destructor must free the objects. According to usual convention, destructors destroy the objects in the reverse order of their creation. Thus, we must override the base class constructor and destructor to create and free our further objects.
    • We also assign initialization values (if any) to member variables in the page constructor.

The following examples provide the basic pattern for virtually anything you will want to do, including implementing page methods. Because you will seldom need to implement page methods (because your application will apply universal processes to pages), our usual obligations will be a subset of the following examples. We will usually only declare formal members in the page class definition, create and free member objects in a page constructor and destructor, and initialize member variables in the constructor:

COMPLETE DELPHI PAGE SUBCLASSING EXAMPLE

You write the class definition in your form's interface section:

DELPHI PAGE CLASS DEFINITION

Copy code to clipboard (JavaScript/IE).

You write the method implementations in the form's implementation section:

DELPHI PAGE CLASS CONSTRUCTOR, DESTRUCTOR, AND METHOD DEFINITIONS

Copy code to clipboard (JavaScript/IE).

We have derived a custom page class in Delphi™.

COMPLETE C++ PAGE SUBCLASSING EXAMPLE

Our C++ example also demonstrates variable declaration and initialization, object instantiation and destruction, overriding the page constructor and destructor, and implementing methods — practically everything you will need to do in a few lines of code. The class definition is written in the form's header (.h*):

C++ PAGE CLASS DEFINITION

Copy code to clipboard (JavaScript/IE).

Method definitions are written in the form's source (.cpp):

C++ PAGE CLASS CONSTRUCTOR, DESTRUCTOR, AND METHOD DEFINITIONS

Copy code to clipboard (JavaScript/IE).

Done.

We have derived our custom page and built an extensible object oriented structure to which we can easily add any further formal page members our application may call for. Having this structure, we can create and place any necessary population of controls in the pattern of these examples. As we modify the derived page class, our modifications are compiled with our project.

PROPAGATING OUR CUSTOM PAGES AT RUN TIME

Our needs were to propagate pages only at run time, and so now we have to configure our page module to propagate our custom page class at run time. (Design time propagation is the next major topic.) To propagate the derived page class at run time, we simply assign our class identifier to PageCreationClass in the form's OnCreate event:

DELPHI EXAMPLE

Copy code to clipboard (JavaScript/IE).

C++ EXAMPLE

Copy code to clipboard (JavaScript/IE).

Now we can even work with our unpopulated module in the development environment. Obviously, you can easily copy these few lines to your unit, revise for your purposes, and hit the ground running in minutes. With these few steps under your belt, you have now mastered custom page propagation for run time implementations. Very little further work will accomplish these same goals for design time page propagation.

C.04.3. DESIGN TIME PROPAGATION — SUBCLASSING A MODULE AND PAGE CLASS IN A PACKAGE

Now we will perform the alternate subclassing case, where we have to propagate custom pages at design time. To do this, we also have to derive and install a module.

To install our module, we need to build it in a Borland Package Library (.bpl), which is a non-visual project that can be installed into the IDE. After installation, our module will appear on our Tool Palette. All we are really doing here is moving our page subclass code into the package and deriving a module to write the one familiar line which assigns our page class' identifier to PageCreationClass:

  • We derive our page class as before.
  • We also subclass the module to write the one line that assigns our page class to PageCreationClass.
  • Lastly, a few clicks perform the installation.

A.  CREATING THE PACKAGE

Creating a new package in the Borland Developer Studio 2006® C++ personality.

  • First, we create a package Project:
    • In Delphi™, Close All and click File->New->Other->Package.
    • In C++Builder, Close All and click File->New->Package - C++Builder.
  • Save the Project (which is the package).

B.1.  CREATING A SOURCE UNIT AND ADDING IT TO YOUR PACKAGE

Now that we have an empty package Project, we need a unit (Delphi .pas or C++ .h* and .cpp) in which to write our source. To create a unit and add it to our package:

  • Click File->New->Unit.
  • In your Package Manager window, add the new unit to your package project.

C++ developers must also perform step B.2. Otherwise, instructions resume at B.3.

B.2.  CRITICAL C++ STEP — ADDING VCL.DCP AND ADVANCE_UPC_PKG.BPI TO YOUR C++ PACKAGE

In C++, you must now add BDS\4.0\lib\release\vcl.dcp and [UPC PACKAGE DIRECTORY]\ADVANCE_UPC_Pkg.bpi to your package or you will get unresolved external reference errors when you compile.

  • Click the Add to Project/Package tool and add both of these files to your C++ package.

B.3.  TEST COMPILE AND SAVE YOUR PACKAGE

  • Compile the empty unit to make sure it generates a .dcu (Delphi™) or .obj (C++).
  • Save.

Now we can write our source.

C.  SUBCLASSING YOUR PAGE MODULE AND PAGE CLASS IN THIS NEW PACKAGE — WRITING THE SOURCE

Except for the module class declaration and its overridden constructor, the following examples are largely the same as our page subclassing examples above. Our examples however do a few things differently, merely for the sake of illustration:

  • Our next examples also show how to publish MyLabel so that a MyLabel property will appear as an expandable node in the Object Inspector.

    You will be able to operate on the label at design time.

  • We subclass TADVANCE_Page_MainComposite to match the PMInterface which will manage our custom page class.
  • Because we are going to install a custom page module into the development environment, another thing that we have to do is write a Register procedure — the name of which must be proper capitalized for compatibility with C++.

THE REGISTER PROCEDURE

  • Register is run by the installation process, and is a simple series of calls which complete the installation, including registration of classes and installation of our TCustomPM01_PageArray module to a MY_ADVANCE_SUBCLASSES node of the Tool Palette.
  • We do not install our page class to the Tool Palette, because the module handles page propagation (you never drop pages onto a module).

Our example Register procedure demonstrates the 3 calls you will have to make, which are intuitive:

DELPHI EXAMPLE PACKAGE UNIT

Copy code to clipboard (JavaScript/IE).

Done. Yes, this is our whole package. We can compile and install the package, drop our custom module onto a form, and it will propagate our custom page class.

Below, we do the same with C++.

C++ EXAMPLE PACKAGE .H HEADER

Copy code to clipboard (JavaScript/IE).

C++ EXAMPLE PACKAGE .CPP SOURCE

Copy code to clipboard (JavaScript/IE).

A few clicks will now install our custom page module.

D.  INSTALLING YOUR PACKAGE

Any testing you need to perform can be done by creating a dynamic instance of your module on a form. Dynamically add an AuxTool that propagates pages, and if things are working, you are ready to install.

After final compiling, you can install:

  • Delphi compiles create .bpl (Borland Package Library) output that you can install with the Install Packages tool or menu option.
  • To install your package from the C++ personality, open the Project Manager, right-click your project, and choose Install. BDS2006 creates the necessary .bpl then.

Our custom page class, module, and installation are finished. You will find your custom page module on a "MY_ADVANCE_SUBCLASSES" node of the Tool Palette whenever you have a form or form contents selected (which is the behavior of BDS).

SUMMARY

Obviously, here too, we can copy this example code into our package, change what we need to, and be off and running in minutes.

This is how to do PMW without having to write redundant parallel infrastructures to do the things you have to do in a conventional application. Wherever we have data which can be formally coupled to pages, subclassing produces the most efficient code base, turnout, and product — PMW software which will process unlimited workspaces from the smallest practical code base and footprint possible.

Master these simple techniques. You will soon practice them enthusiastically and artistically — and program circles around your competitors.

RELATED TOPICS

© Copyright 1995-2007, by ADVANCE Information Systems, Inc. ALL RIGHTS RESERVED.Copyright 1995-2007, by ADVANCE Information Systems, Inc. ALL RIGHTS RESERVED.

Firefox™.Best viewed in Mozilla Firefox™.