Warung Bebas

Sabtu, 03 Desember 2011

Putting the pieces together - the DSharp presentation model

Recently there have been several threads and discussions about separating GUI and business logic. Of course that is nothing new, the MVC or MVP patterns are widely known. There is just one problem especially with these two patterns: the dependency between the presenter or the controller and the view and model. Often you cannot easily change the view. Testing the model and the controller or presenter without the view is complicated or impossible.

This is where the MVVM pattern has its benefits. Of course in WPF this pattern can shine because you have XAML but also in Delphi this pattern is worth a closer look. This pattern is also known as Presentation Model. The main benefit over MVC or MVP is that view and viewmodel have no dependency on each other. You can design the GUI independently from the business logic and just bind them together later. And there is the keyword that makes this pattern work: binding.

There are several amazing frameworks for MVVM development in .Net like Caliburn Micro or Prism. Caliburn consists of several different pieces to easily build applications the MVVM way. One of them is the ModelViewBinder. It takes the view (like a form or a frame) and a viewmodel (like a datamodule or another business object) and creates bindings to connect these two using naming conventions. For example the Lastname property of the viewmodel is bound to the Lastname edit on the view, the Orders property which may be a list gets bound to a listview called Orders. You could also bind the Customer.Address.Street property to the Customer_Address_Street edit on the view. All this gets powered by a DI container that puts all the pieces together, also by convention over configuration. If you have a viewmodel called CustomerDetailsViewModel there should be a CustomerDetailsView (there are actually several different conventions and you can add more if you like).

DSharp presentation model makes it possible to use the powerful spring DI container and data bindings to easily build applications that are easy to test and to develop. It sure is just scratching the surface yet but when you take a look at the ContactManager sample in the svn repository you can get a basic idea of what is possible.

Enough with just talking theory. Let's create a simple application!

Step 1 - The View

After creating a new VCL application (FMX is pretty similar - there are just not that many controls supported out of the box yet) we add some controls on the form so it looks like this:


After saving the unit the source looks like this:

unit CalculatorViewForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DSharp.Bindings, DSharp.Bindings.VCLControls;

type
TCalculatorView = class(TForm)
BindingGroup1: TBindingGroup;
CalcOperator: TComboBox;
CalcResult: TEdit;
Calculate: TButton;
Label1: TLabel;
LeftOperand: TEdit;
RightOperand: TEdit;
Error: TEdit;
end;

implementation

{$R *.dfm}

initialization
TCalculatorView.ClassName;

end.

The only thing you actually have to write there after adding the components including the binding group is adding the unit DSharp.Bindings.VCLControls.pas (only if you don't have DSharp bindings VCL designtime package installed which inserts that unit automatically) and the line in the initialization part of the unit. This is necessary so the linker does not remove this class because it actually is not referenced anywhere (similar to the RegisterComponent you do if you are working with the DI container in the classic way).

Step 2 - The ViewModel

Now we create the viewmodel - the class that actually does the work and holds all the states of for the UI.

For our simple calculator we just need some fields and a method:

unit CalculatorViewModel;

interface

uses
CalculatorInterfaces,
DSharp.PresentationModel.ViewModelBase,
SysUtils;

type
TCalcOperator = (Add, Subtract, Multiply, Divide);

TCalculatorViewModel = class(TViewModelBase, ICalculatorViewModel)
private
FLeftOperand: Double;
FRightOperand: Double;
FCalcOperator: TCalcOperator;
FCalcResult: Double;
FError: string;
public
procedure Calculate;

property LeftOperand: Double read FLeftOperand write FLeftOperand;
property RightOperand: Double read FRightOperand write FRightOperand;
property CalcOperator: TCalcOperator read FCalcOperator write FCalcOperator;
property CalcResult: Double read FCalcResult write FCalcResult;
property Error: string read FError write FError;
end;

implementation

{ TCalculatorViewModel }

procedure TCalculatorViewModel.Calculate;
begin
try
case FCalcOperator of
Add: FCalcResult := FLeftOperand + FRightOperand;
Subtract: FCalcResult := FLeftOperand - FRightOperand;
Multiply: FCalcResult := FLeftOperand * FRightOperand;
Divide: FCalcResult := FLeftOperand / FRightOperand;
end;
FError := '';
except
on E: Exception do
begin
FError := E.Message;
FCalcResult := 0;
end;
end;
DoPropertyChanged('CalcResult');
DoPropertyChanged('Error');
end;

initialization
TCalculatorViewModel.ClassName;

end.

We inherit from the TViewModelBase class which has some mechanics built-in that we need for the whole thing to work (like inheriting from TComponent which is necessary for the lifetime management and implementing several interfaces the framework needs). If you are just interested in creating something similar to the passive view and constructing the classes yourself and just using DSharp bindings you can just inherit from TPropertyChangedBase or some other class and wire things up yourself (or use the ViewModelBinder to do that without the rest of the framework).

We actually have no setters for the properties in this viewmodel because the changes just happen when you click the calculate button. Did you notice we named the properties exactly like the controls? That is not by accident. As I told you earlier the ViewModelBinder looks for components and properties it can bind together and it does it by their names.

One thing here is not obvious. We added the 4 operators to the items of the combobox earlier and we named them exactly the same (not the classic hungarian notation for enums here though). This is because the ViewModelBinder binds to the Text property of a TComboBox (you can change that to ItemIndex if you like - just edit the line in DSharp.PresentationModel.VCLConventionManager.pas). Then you can name the enums and the items differently. This may also make more sense when you localize the items in the combobox but it depends on how the combobox is set up. In the future the ViewModelBinder might consider the options of the combobox.

The calculation method is actually pretty simple. At the end it sends the notifications of the changed properties: CalcResult and Error. Keep in mind that this example does not show any kind of validations. You can still write rubbish into the edits. Since internally a conversion is done from string to double (bindings have a default value converter unless you specify one yourself) the value is not sent to the viewmodel if the converter cannot convert it into a double. How validations can be done and shown in the UI can be seen in the Validations sample.

Now let's take a look at the last unit of this example:

unit CalculatorInterfaces;

interface

uses
DSharp.ComponentModel.Composition;

type
[InheritedExport]
ICalculatorViewModel = interface
['{03AF7AE1-CCDF-4F06-9074-919F6C759DBE}']
end;

implementation

end.

The InheritedExport attribute tells the DI container to register every class it finds that implements this interface. That is why we had to add the line in the initialization part of the viewmodel unit. Because that class also is referenced nowhere and the linker would just throw it out. Why not doing the registration of the class there instead? That would actually create a dependency on the DI container and you have no chance to remove that registration for unit testing without code changes.

Remember to add a guid to the interface, otherwise the DI container will complain.

But wait - that interface does not have any methods. Yes, for our example it actually does not need them. Because bindings currently can only work between objects the interface is cast to the implementing object behind the scenes. Keep in mind that you cannot do interface delegation and binding to such interfaces. Also in our example we don't call anything on that interface because the Calculate method (of the object) is bound to the button. For a more complex scenario and actually using the interface methods look at the ContactManager example.

Step 3 - Putting the pieces together and starting up

Let's take a look at the dpr file now:

program Calculator;

uses
Forms,
CalculatorViewForm in 'CalculatorViewForm.pas' {CalculatorView},
CalculatorViewModel in 'CalculatorViewModel.pas',
CalculatorInterfaces in 'CalculatorInterfaces.pas',
DSharp.PresentationModel.VCLApplication;

{$R *.res}

begin
Application.Initialize;
Application.Start<ICalculatorViewModel>;
end.

We have to make some modifications here. By adding the DSharp.PresentationModel.VCLApplication (or FMXApplication) unit we add the Start<T> method to TApplication. That is the method that starts it all up as the name implies. You have to specify the root viewmodel of your application. From there on you can build everything you like - manually or using the presentation model. We removed the other methods except Application.Initialize. I have to admit that I am not totally happy with that solution right now because it kind of breaks something in the IDE regarding editing the project options (only things like Title that result in the IDE editing the dpr file). Everything else still works, no worry. The Initialize call has to stay there because removing it would actually remove the theming from the application (another weird thing related to the source of the dpr file).

Let's start the application and look if it works (if you don't want to do all the steps by yourself you can find the source in the repository as always). For now I am not going into the implementation details - I leave that for a future post to explain how all the different pieces are working together to make it a bit clearer and less "magic".

DSharp presentation model is available for Delphi 2010 (without aspects) and higher (working on 64bit for XE2) - if you experience any problems feel free to send me an email or file an issue on the project page - I usually test it on all platforms but sometimes some of the nasty compiler or rtl bugs may break something.

0 komentar em “Putting the pieces together - the DSharp presentation model”

Posting Komentar

 

Indah Hidup Copyright © 2012 Fast Loading -- Powered by Blogger