summary
as you can see, with just a little code having been written, we have achieved our aim of creating a framework with independent modules.

of course, this application framework doesn't have a lot of features and in the real world you will need to enlarge it. for example, in most applications i've written, there was a need to provide module security. based on security privileges, the module may be accessible or not by the end-user. this is quite easy to introduce by just writing code in the module registration method. the base module doesn't implement any features and this is not normal either. in most cases, you will need to introduce functionality directly into the base module. the only thing you have to remember is that any feature introduced into the base module will appear in the rest of the modules automatically. thus, you want to provide a module inheritance scheme, for example: custommodule -> customdbmodule -> customgridmodule etc., where every module adds functionality.
here is the link to the source code of the application written in delphi.
introduce actions
in the previous steps we built a small application framework that allows us to create independent modules. now it is time to think about adding functionality to the modules and the first problem that we need to resolve is how to create the layer between ui objects in the main form and the business code in the modules.
in other words, we want to have a menu and toolbar system on the main form. menu items and toolbar buttons need their visible, enable and other properties to reflect the business logic contained in the module currently displayed. menu items and toolbar items should not know details of the module displayed and modules should not be aware of the existence of menus and toolbars at all. we even want to be able to change ui controls, e.g. move from a standard menu system to the developer express expressbars library or vice versa without changing the code in the modules. also, we want to be able to test module functionality in a test engine that does not even create a main form.
basically, we need one more layer between the ui on the main form and the business code in the modules. i call this layer actions.
vcl has a native action layer. we can't use it as is though, because it would destroy modules independently from our application. however, we can write code around the vcl actions library to do everything we need.
the action module is really simple using the vcl actions library. first, create a new data module class. drop a tactionmanager component on it, write code within the tactionmanager's execute event and write several lines of code around data module class. to add a new action, you just have to create a new action in the actionmanager component. to bind the action with the ui control, you have to assign its action property to the appropriate action component, your ui object has to support actions, of course. standard vcl and developer express controls all support actions technology.
| [delphi] unit dmactions; interface uses sysutils, classes, appevnts, xpstyleactnctrls, actnlist, actnman; type tdmappactions = class(tdatamodule) actionmanager: tactionmanager; action1: taction; action2: taction; action3: taction; procedure actionmanagerexecute(action: tbasicaction; var handled: boolean); private function getactioncount: integer; function getaction(index: integer): tbasicaction; // vcl actions disable ui controls if they have no events assigned // the easiest solution to avoid this is to assign a dummy event procedure dofakevclaction(sender: tobject); procedure fakevclactions; public constructor create(aowner: tcomponent); override; // return the action count property actioncount: integer read getactioncount; property actions[index: integer]: tbasicaction read getaction; end; var dmappactions: tdmappactions; // returns the global instance of actions class function appactions: tdmappactions; implementation uses forms, modules; {$r *.dfm} // returns the global instance of actions class function appactions: tdmappactions; begin if(dmappactions = nil) then dmappactions := tdmappactions.create(application); result := dmappactions; end; { tdmappactions } constructor tdmappactions.create(aowner: tcomponent); begin inherited create(aowner); fakevclactions; end; procedure tdmappactions.fakevclactions; var i: integer; begin for i := 0 to actioncount - 1 do actions[i].onexecute := dofakevclaction; end; procedure tdmappactions.dofakevclaction(sender: tobject); begin //do nothing end; function tdmappactions.getactioncount: integer; begin result := actionmanager.actioncount; end; function tdmappactions.getaction(index: integer): tbasicaction; begin result := actionmanager.actions[index]; end; // handler for execute event of actionmanager component procedure tdmappactions.actionmanagerexecute(action: tbasicaction; var handled: boolean); begin // call the executeaction method of the currently showing module if (moduleinfomanager.activemoduleinfo <> nil) then handled := moduleinfomanager.activemoduleinfo.module.executeaction(action); end; end. |