|
Software Development Kit to the Delphi-Win32 and Free Pascal compilers |
| Home > Wiki > MVP |
MVPenglish (en) | português (br) The Model-View-Presenter (MVP) framework provided is based on the MVP design pattern. Reference for background reading can be found at this Taligent paper and this Martin Fowler's article.
[edit] Presenter classesAny form that presents business objects needs a Presenter. The Presenter informs the MVP framework which business class it represents and provides the mapping between each control and its associated attribute. There are two mandatory tasks to configure a Presenter:
[edit] Configuring sub-presentersAs an example, a Presenter to the following TClient class: TClient = class(TPressObject) Name: String; City: Reference(TCity); TCity = class(TPressObject) Name: String; has the following syntax:
TClientPresenter = class(TPressMVPFormPresenter)
protected
procedure InitPresenter; override;
end;
...
procedure TClientPresenter.InitPresenter;
begin
CreateSubPresenter('Name', 'NameEdit');
CreateSubPresenter('City', 'CityComboBox', 'Name');
end;
That's all. CreateSubPresenter will create a Presenter to manage the presentation of the NameEdit and CityComboBox controls. Its first parameter is the name of the attribute, the second is the name of the form's control and the last one, mandatory only to Structure attributes (compositions, aggregations and collections), is which attribute of the target class you want to present in the form's control. Here is another example: TInvoice = class(TPressObject) IssueDate: Date; Client: Reference(TClient); Items: Parts(TInvoiceItem); Total: Currency; TInvoiceItem = class(TPressObject) Quantity: Integer; Product: Reference(TProduct); Total: Currency; TProduct = class(TPressObject) Name: String; A TInvoicePresenter.InitPresenter method implementation for the TInvoice class would be:
procedure TInvoicePresenter.InitPresenter;
begin
CreateSubPresenter('IssueDate', 'IssueDateEdit');
CreateSubPresenter('Client', 'ClientComboBox','Name');
CreateSubPresenter('Items', 'ItemsStringGrid','Quantity;Product.Name;Total');
CreateSubPresenter('Total', 'TotalEdit');
end;
The third parameter describes which attributes from the Items objects need to be presented in the grid. You can also define the width (in pixels) and the header caption in this parameter as follows: 'Quantity(10,"Qty");Product.Name(200,"Product Name");Total(80)' [edit] Registering presentersPresenters need to be registered with the MVP framework. Registration provides the mapping between the business objects, forms and their related Presenters. An example: within MVP class unit initialization TClientPresenter.RegisterBO(TClient, [fpExisting]); within the Form unit implementation uses ClientMVP, PressVCLBroker; // Delphi uses ClientMVP, PressLCLBroker; // Lazarus ... initialization PressVCLForm(TClientPresenter, TClientEditForm); // Delphi PressLCLForm(TClientPresenter, TClientEditForm); // Lazarus The first parameter of the RegisterBO method indicates the class of the business object, the second is a set of options indicating the purpose for which this Presenter is used. These options are fpNew, fpExisting and fpQuery. [fpNew] - all presenters registered with this option will match searches when the framework needs to create a form for a new object. For example this option would be used to present a form when adding a new invoice item in a grid control; [fpExisting] - all presenters registered with this option will match searches when the framework needs to create a form for an existing object. For example this option would be used to present a form for editing an existing invoice item; [fpQuery] - used to match presenters that show options to populate references attributes, eg: TJob = class(TPressObject) Name: String; PrintingColors: References(TColor); So, when the user needs to include more items in the PrintingColors attribute, presented via a StringGrid control, a fpQuery presenter will be called. [fpNew, fpExisting] - the same form will be used for both new and existing objects. This is the default parameter setting. [] - the presenter will never match the framework searches initiated by user actions. Useful when the programmer needs to map a form to a business class and will call the form manually. [edit] Presenter of the main formA Presenter for the main form is only necessary if the form is associated with a business object or if the framework is used to bind commands to buttons or menu items. Creating a main Presenter class is as simple as creating the other Presenters: TMainPresenter = class(TPressMVPMainFormPresenter) protected procedure InitPresenter; override; end; ... procedure TMainPresenter.InitPresenter; begin BindCommand(TNewUserCommand, 'NewUserMenuItem'); end; The Presenter of the main form doesn't need to be registered, however it must be called in the main project file. Note: If there is no main form Presenter, PressApp.Run should be called instead in the main project file. See Creating application for further details about configuring a PressObjects application. [edit] Model classesThe model class controls the presentation of the business object and its attributes: TClientModel = class(TPressMVPObjectModel) protected procedure InitCommands; override; end; ... procedure TClientModel.InitCommands; begin inherited; AddCommand(TClientReport); end; ... initialization TClientPresenter.RegisterFormPresenter( TClient, TClientForm, [fpNew, fpExisting], TClientModel); This piece of code (above) is used to include a command in the context menu of the client editing form. Another example, that creates a Model for an attribute:
TClientDueDateModel = class(TPressMVPDateModel)
protected
procedure InitCommands; override;
end;
...
procedure TClientDueDateModel.InitCommands;
begin
AddCommands([TClientDueDateCommand, nil]);
inherited;
end;
...
procedure TClientPresenter.InitPresenter;
begin
...
CreateSubPresenter('DueDate', 'DueDateEdit', '', TClientDueDateModel);
end;
This example introduces a command as the very first item of the context menu of the DueDateEdit control. The nil parameter includes a menu separator after the command. [edit] View classesViews are used by the MVP framework to provide the behavior and information transfer for visual components. For example: TListViewView = class(TPressMVPItemsView) // methods and members used to manage a TListView control // <<see Source/Core/PressMVPView.pas file for implementation samples>> ... initialization TListViewView.RegisterView; After a View is registered, the framework will be able to use it whenever a form, containing the control that it manages, is presented. It is possible to create and register another View that manages a control already managed by the framework. To do this it is necessary to inherit directly from the view class that is already registered to manage the control then override the appropriate methods to modify its behavior as desired. Not using this process will result in an exception being raised because of ambiguity. As an example, if the following is declared: TMyOwnEditView = class(TPressMVPWinView) ... end; and registered, an exception will be raised because TPressMVPEditView is already implemented and registered by the framework to manage a TEdit control. There are two Views managing the same control but the framework has no way of knowing which one to use. But if the following is declared: TMyOwnEditView = class(TPressMVPEditView) all is well now as the framework checks that the new class has inherited from TPressMVPEditView and uses it instead. [edit] Command classesCommands are widely used to create new functionalities for forms. TMarkClientAsGoodClientCommand = class(TPressMVPObjectCommand) protected function GetCaption: string; override; function GetShortCut: TShortCut; override; procedure InternalExecute; override; function InternalIsEnabled: Boolean; override; end; ... function TMarkClientAsGoodClientCommand.GetCaption: string; begin // only useful when creating commands for models (context menu) Result := 'Mark this client as a good one!'; end; function TMarkClientAsGoodClientCommand.GetShortCut: TShortCut; begin // only useful when creating commands for models (context menu) Result := VK_F9; end; procedure TMarkClientAsGoodClientCommand.InternalExecute; begin inherited; (Model.Subject as TClient).IsGood := True; end; function TMarkClientAsGoodClientCommand.InternalIsEnabled: Boolean; begin Result := not (Model.Subject as TClient).IsGood; end; The model property inside a command points to the Model that it belongs to. The Subject property of the Model points to the business object or an attribute, corresponding to the type of the command's owner (a Model). Commands can be assigned to menu items in main menus, "clickable" controls, or in the Model, presented as context menu items: procedure TClientPresenter.InitPresenter; begin ... BindCommand(TMarkClientAsGoodClientCommand, 'ClientIsGoodSpeedButton'); BindCommand(TMarkClientAsGoodClientCommand, 'ClientIsGoodMenuItem'); end; [edit] Interactor classesAn Interactor is used to modify a Presenter's behavior:
TKeyboardInteractions = class(TPressMVPinteractor)
protected
procedure InitInteractor; override;
procedure Notify(AEvent: TPressEvent); override;
public
class function Apply(APresenter: TPressMVPPresenter): Boolean; override;
end;
...
class function TKeyboardInteractions.Apply(APresenter: TPressMVPPresenter):
Boolean;
begin
// Is this interactor interested in this Presenter?
// yes, any presenter
Result := True;
// or depends, only if the presenter manages a CheckBox view
Result := APresenter.View is TPressMVPCheckBoxView;
end;
procedure TKeyboardInteractions.InitInteractor;
begin
// what does the interactor need to do just after it is created?
// let's listen for keyboard events from the view
Notifier.AddNotificationItem(Owner.View, [TPressMVPViewKeyDownEvent]);
end;
procedure TKeyboardInteractions.Notify(AEvent: TPressEvent);
begin
// what does the interactor need to do when the notification is called?
// Note: multiple events in the second parameter of the
// AddNotificationItem means multiple AEvent classes
if AEvent is TPressMVPViewKeyDownEvent then
begin
TPressMVPViewKeyDownEvent(AEvent).Key; // has the key code
TPressMVPViewKeyDownEvent(AEvent).Shift; // has the shift state
end;
Owner; // points to the presenter
Owner.Model; // points to the model
Owner.Model.Subject; // points to the business object
Owner.View; // points to the view
end;
...
initialization
TKeyboardInteractions.RegisterInteractor;
|
Personal tools |