A Simple start with MVP in Delphi for Win32, Part 1

Posted on gio 12 febbraio 2009 in Design Patterns

As GUI framework such as VCL become more and more powerful, it's common practice to let the UI layer do more than it should. Without a clear separation of responsibilities, the UI layer can often become an integral part of application and businness logic, but... this kind of responsabilities belongs to other layers of the application.\ A design pattern (and his numberless variants), is especially well suited to solving this problem.

In this article I want to build a simple application using MVP pattern. Actually, pattern used is not "clear" MVP but his variation called Passive View.

Using Fowler words:

A perennial problem with building rich client systems is the complication of testing them. Most rich client frameworks were not built with automated testing in mind. Controlling these frameworks programaticly is often very difficult.

A Passive View handles this by reducing the behavior of the UI components to the absolute minimum by using a controller that not just handles responses to user events, but also does all the updating of the view. This allows testing to be focused on the controller with little risk of problems in the view.

Passive View ensures no dependecies between Model and View.\ \

In this sample, "model" is a simple layer for application logic. In real world, "service layer" should incapsulate "application service" and "domain model".

Application looks like following:

\

\

\

Connect View and Presenter\ The view (the Form in VCL application) must implement an interface.

This interface should provide all method to interact with GUI:

``` {lang="delphi"} ICalculatorView = interface ['{471E3657-C6CE-49A3-BCB4-8FA6AF611DAD}'] function FirstOperand: String; function SecondOperand: String; procedure SetFirstOperand(Value :String); procedure SetSecondOperand(Value :String); function GetOperator: IGUISelectableList; procedure SetCalcResult(const Value: String); procedure SetCalcResultReadOnly(const Value: Boolean); function Error: IGUIEdit; end;

For simple interacting with GUI widget (in our example are EditFirstOperand, EditSecondoperand and EditCalcResult) we use a simple methods like following

``` {lang="delphi"}
  function FirstOperand: String;
  function SecondOperand: String;
  procedure SetFirstOperand(Value :String);
  procedure SetSecondOperand(Value :String);

But, if we need more by our widget (like populating combo box or change font color in the EditError or set ReadOnly to true) we should use another interface for a family of component.\ In this sample I wrote 3 general interface:

``` {lang="delphi"} IGUIBaseInterface = interface ['{F0B7F031-9302-415E-8545-1FE20A365840}'] end;

IGUIEdit = interface(IGUIBaseInterface) ['{FE2D56FB-0CFB-4B33-9B56-0A523B235D37}'] procedure SetText(const Value: String); function GetText: String; function GetAsInteger: Integer; function GetAsFloat: Extended; procedure SetReadOnly(const AValue: boolean); procedure SetVisible(const Value: Boolean); function GetTextAsInteger: Integer; procedure SetTextAsinteger(const Value: Integer); function GetTextAsFloat: Extended; end;

IGUISelectableList = interface(IGUIBaseInterface) ['{EEFE5C52-94C3-464B-80F2-05E443B0F0F6}'] procedure SetText(const Value: String); function GetText: String; procedure SetValue(const Value: String); function GetValue: String; function GetSelected: ISSKeyValue; procedure AddPair(AKey, AValue: String); procedure Clear; end;

For implementation details see attached sample code.

Finally in FormCreate of our form we can wire Presenter and View:

``` {lang="delphi"}
TfrmCalculatorView = class(TForm, ICalculatorView)
  //code 
end;
  //interface section
procedure TfrmCalculatorView.FormCreate(Sender: TObject);
begin
  //Link controls with related interface
  IOperators := TGUISelectableList.Create(ComboOperators);
  IError := TGUIEdit.Create(EditError);

  //link view and presenter
  //In this version VIEW know PRESENTER
  FPresenter := TCalculatorPresenter.Create(Self);
end;

This is a very simple example, so not all looks like real world. In a real world application, for example, view should not known the presenter class. With dependency injection you can do that (Next article in this serie will talk about this).

Every event generated by View (our Form) must be redirected to Presenter.

{lang="delphi"} procedure TfrmCalculatorView.Button1Click(Sender: TObject); begin FPresenter.DoCalc; end;

Another approach is to publish some events in view interface and let presenter to bind them via standard event handler or anonimous methods (but this is for another post).

In attached sample code there is a sample application and unit test for Service Layer and View.\ Required Mock Library is included in the zip file.

Simple Passive View, Sample Code

In 2nd part I'll talk about unit test and mock object in Passive View.