Donnerstag, 26. April 2018

MVVM 2.0 - I did it my way.

If you like, you can compare this with one of my older posts from 2016: MVVM - Or what I think MVVM is.  (Translated)

Why MVVM?

  • Because it’s cool and I’m a geek
  • to show – I’m better than other developers
  • so nobody else could maintain the code

no – perhaps – NO!!! Just joking!

Because we like to
  • separate forms from code
  • to get better maintainable code
  • have less-hardcoded dependencies
  • to be able to test the business logic
  • to test the workflow

Sure? Are you writing tests? If not – stop reading…
 
But perhaps you like to develop code you can use again in other applications…
 
Ask 10 developers to explain MVVM – at first; you will get a picture from dotnetpattern.com, msdn.microsoft.com or wikipedia.org, then everybody tells you: “This is the pattern and “so” it has to be implemented”
 
OT: Like many other patterns… You have to follow the rules of this 4 guys and the book from 1994! More than 500.000 copies of the book have been sold – not so bad at all…
 
Back to MVVM - If you ask for the details, you will get 10 ideas how to implement it.
 
But… We are Delphi developer – why should we try to implement things as Microsoft did in .net? Because this is the right way?

Let us dive into:
The core elements are View, Viewmodel, and Model. At the beginning, you could trade a TForm as the View, but this is not the same. A TFrom could be the container for many views at the same time. For the moment, we say TForm = View – the key things of MVVM are the bindings or better, the communication from Viewmodel to View and back. (And perhaps to the Model)
 
If we follow the rules – the view should have no logic, the Viewmodel is responsible for handling the view-logic and converting the data to the View and the Model contains the data. (hope this is right) I never took this approach.
 
Our view is not a stupid xml-only-description of visual controls. Our controls always have their own logic, we have styles and animations, able to do onMouseover/down/up things. Trigger doing fancy stuff.
 
The Model is dealing with the data and the Database, too? I don’t think so. What is a database?
 
Neither my ViewModel nor my Model know what a database is. The Models gets an interface to store or load data – without knowing where it ends.
 
How does the communication work?
 
The Model changes some data and now the ViewModel wants to inform the View or perhaps all Views, about this change.
 
At first, we need a Multicast event to inform more than one View about the new data. So every view has to sign in for the event. Now the Viewmodel could send a PropertyChanged Event like:
 
PropertyChanged(PersonNameProperty);

Every view – that is able to show the change, gets the Event and could ask the ViewModels Property PersonName.

What?

PersonNameProperty is defined as:
Const PersonNameProperty : String = 'PersonName';

Use Consts and no magic String so we always have the right typo. OK…That is good, but:

In the View we end up in a

procedure PropertyChanged ( Const APropertyChanged : String);

Comparing with many If then else constructions (first bad thing) and because we have a const in the Viewmodel, the reference is not the same so the string-compare must compare all chars. (second bad thing).
 
If we have a huge view (yes we could perhaps split it) we and up with a too long comparing procedure.

Since Windows 3.1 - in the early days – Messages are send with the content or at least with a pointer to the content.
 
So why are we just sending change “hints”? This is like sending a SMS – I have news call be back, instead of “I will be late, arriving at 8pm”.

Sending Strings is good for testing. Eg: A property change of PersonName := 'NewName'; should fire 'PersonName' – I tried Const ID’s  like Const idPersonName : Integer = 42; Not so good for testing but you can use a case at the View.

I don’t like to repeat on every Property:
 
begin
  if FPersonName <> AValue then
    begin
      FPersonName := AValue;
      PropertyChanged(PersonNameProperty);
    end;
end;
 
Same in every setter.

 
Then to the View:
if APropertyChanged = TPersonViewModel.PersonNameProperty then
  PersonName.Text := FViewModel.PersonName;

I first implement the MVVM Pattern the MS-way, but if you think – “To much writing” or “I did not test my code” – you are right and of course, debugging is not so easy, too…


In fact, development time takes a bit longer. This extratime cut’s down my Test-writing-Time… (Bad thing three), because I love TDD.

It is faster if you could use the same Model or perhaps the Viewmodel in another project, but that is another story.
 

These problems lead me to “my way” MVVM 2.0…


We have attributes and the RTTI!

I think: The best way to use a pattern is if the pattern is not so far from you normal workflow.


I like to design my Forms and so my Views as Forms, too – Frames are bad and often leads to problems with the IDE. So SubViews are Forms with a TLayout-Container that parent is mapped to the target-parent at runtime.

My new workflow is:
  1. Create a Form/View
  2. Change Class(TFrom) to Class (TMVVMForm) / or Frame for SubViews
  3. Put attributes at FormControls like [ViewModelLink] PersonName : TEdit;
  4. Create Procedures with attributes like
    [PropertyChange]
    Procedure PersonNameChanged(Const AValue : String);
  5. Register the Form at the ViewLocator with the necessary ViewModel
  6. That’s it.

In your NavigationService you could get the View from the ViewLocator for a given ViewModel and an optional Name. On Creation the View connects all bindungs and propertyChanges.
 
 
You like to change Names? All attributes take optional name parameters. For:
 
 
[ViewModelLink(TPersonViewModel.PersonNameProperty]
Edit1: TEdit; // Better rename this!
 
 
Now to the ViewModel:
 
  1. Create a class TPersonViewModel = Class(TRootViewModel)
    I don’t like ViewModelBase as Name – it sound like a database for ViewModels…
    All my DBClasses ends with Base – PersonBase not DBPerson!
  2. Define your private Fields as
    FPersonName : autochange<string> // FPersonName : String
  3. Property PersonName : String : read GetPersonName write SetPersonName;
  4. Procedure SetPersonName(Const AValue : String);
    begin // Auto-PropertyChanged if different.
      FPersonName.Value := AValue;
    end;
  5. That’s it.
 
 
Most of the stupid code writing is not necessary anymore and done in the background over the RTTI.
 
 
Of course, this is only a small part of this pattern, but now I can point my focus on the more advanced parts.
 
 
You like this approach? – Please leave a comment – if not…;-)