Freitag, 15. Februar 2019

Be carefull with inline vars.

The new inline var is a great feature. You can save time and your code looks more cleaner.

But are there any problems with inline vars and the auto-type functionality?

Let's take a look at a small example.

Procedure Foo
  i : int32;
  x : uint32;
  x := 0;
  for i:=0 to x-1 do
    Bar(i); // Bar would never be called.     

Procedure Foo;
  i : int32;
  for i:=0 to MyList.Count - 1 do
    DoSomeThing(MyList[i]); // Should work as expected

Procedure Foo;
  for var I := 0 to MyList.Count - 1 do
    DoSimeThing(MyList[i]); // Are you sure this is working?

At this point you are unable to decide!!! You have to take a look at the function result of Count. if the result is an uint32 this is not working, because the compiler takes an uint32 for i and the for is running "forever" ( from 0 to $FFFFFFFF ).

If your implementation of this list auto-creates elements on read access you have a problem...

Dienstag, 12. Februar 2019

Simple 3D Hello World in Firemonkey!

Games with FMX!

Is Firemonkey ready for game development - of course! There are many games out in the wild written with delphi and also with FMX.

Why this blogpost? Because in the last weeks there are so many questions in forums or FB about this topic.

But where to start?

Do you want to go 2D? 

Great simple answer:

1.) Use a TRectangle with a PNG for your sprite(s).
2.) Use a Timer with 16ms for your game-loop.

Go and write - like me in 2015 - funny games like this:

Perhaps some time in the future I will release this game, if I find the time and change the stolen ROM-Images with new creations. ;-)

Do you want 3D? 

Then you might want to use a "3D Engine". Why an engine, because you do not want to do the hard work for all platforms by hand. Or you just use FMX as your engine.

This could look like this:

  1. Create a new FMX 3D application.
  2. Put a dummy object on the form
  3. Put a camera on the form
  4. Put a light on the form
  5. Place the camera at 0,0,-20
  6. Set the light to Point
  7. Place the light at 0,0,-20
  8. Put a Timer on the form and set it to 16ms
  9. In the onTimer event call Invalidate;
  10. In the onRender event call your 3D stuff.
The Form should look like this:

  THello3DWorld = class(TForm3D)
    Dummy1: TDummy;
    Camera1: TCamera;
    Timer1: TTimer;
    Light1: TLight;
    procedure Timer1Timer(Sender: TObject);
    procedure Form3DCreate(Sender: TObject);
    procedure Form3DDestroy(Sender: TObject);
    procedure Form3DRender(Sender: TObject; Context: TContext3D);
    { Private-Deklarationen }
    fColorMaterial : TColorMaterial;
    fRotate        : TMatrix3D;
    fVertexBuffer  : TVertexBuffer;
    fIndexBuffer   : TIndexBuffer;
    Procedure DoInit;
    Procedure DoRotate;
    { Public-Deklarationen }

In the FormCreate you can create, like in the example - a 3D object. I use a simple Triangle, but you can of course create any 3D Mesh or object. You also have to create a Material. You can use your own Material (shader) or use the build in.

This looks like this:

procedure THello3DWorld.Form3DCreate(Sender: TObject);
  fColorMaterial := TColorMaterial.Create;
  fModelMatrix   := TMatrix3D.Identity;

  // 3 for a Triangle 
  fVertexBuffer := TVertexBuffer.Create([TVertexFormat.Vertex],3);
  fIndexBuffer  := TIndexBuffer.Create(3,TIndexFormat.UInt16);

  Timer1.Enabled := true;

In the DoInit, I setup a triangle for this little demo:

procedure THello3DWorld.DoInit;
  lVector : TVector3D;
  lMatrix : TMatrix3D;
  // Create a Rotation Matrix for a equilateral triangle
  lMatrix := TMatrix3D.CreateRotationZ(120*Pi/180);
  LVector := TVector3D.Create(3,0,0); // Size 3

  fVertexBuffer.Vertices[0] := LVector;

  LVector := LVector * LMatrix; // Rotate to next point

  fVertexBuffer.Vertices[1] := LVector;

  LVector := LVector * LMatrix; // Rotate to next point

  fVertexBuffer.Vertices[2] := LVector;

  // Draw Clockwise
  fIndexBuffer.Indices[0]   := 0;
  fIndexBuffer.Indices[1]   := 1;
  fIndexBuffer.Indices[2]   := 2;

  // Create Rotation Matrix for later use 
  fRotate := TMatrix3D.CreateRotationZ(3*Pi/180);

In the on Form3DRender event you can provide all your generated objects to the renderer.

procedure THello3DWorld.Form3DRender(Sender: TObject; Context: TContext3D);
  if ( Context = nil ) or not( Context.BeginScene ) then

    Context.SetContextState( TContextState.cs3DScene );
    Context.Clear( TAlphaColorRec.Black ); // or any Color

    // Set ModelMatrix
    Context.SetMatrix( fModelmatrix );

    fColorMaterial.Color := TAlphaColorRec.Yellow;

    // Render Model
    Context.DrawTriangles( fVertexBuffer, fIndexBuffer,
                                          fColorMaterial, 1 );

Thats it. Now you FMX program could render any 3D object to the scene.

For entertainment the object should rotate or do whatever you like. Thats why this demo has a DoRotate method.

procedure THello3DWorld.DoRotate;
  fModelMatrix := fModelMatrix * fRotate;

This is a simple Matrix3D rotation. To use this rotation just call DoRotate before you call the invalidate. Normally you would measure the time difference between two frames and do the calculation for this deltaT to get even rotation speeds on all platform / CPUs. But with our timer we are fine for this demo.

With a collection of elements and some logic you can build your own 3D Engine and create some nice 3D stuff or games.

If you like to see more 3D Stuff write me a message.

Samstag, 9. Februar 2019

The lovely Microsoft hotline...

A very good hotline is the most important part of a software company (if you really on customer satisfaction).

I updates my Mainboard, CPU, RAM. Everything was ok...

After that I copied my VM from a single M.2 to a Raid-Stripe-Set of M.2's.

An suddenly : Office wants activation an Windows, too

First try on the Microsoft Hotline. A very grumpy man: Your Windows Key is not Valid ( I've used this key since upgraded from Windows 8 ). You have to buy a new Key.  Have a nice day. Tuuuuuut. He just hang up.

Second try: A not so grumpy man - more helpfully - No problem Sir, please gibe my the 8 Digits from this 8 groups. Two try's. and than the same answer: You have to buy a new Key. Have a good day. By By.

And now - Windows wants a new key and office, too

Amazon : New Windows Key for 1,48€ -  yes under 2€ - the hotline cost were higher!
Windows working.

eBay : New Office Key for 7,25€ - upgraded my office from 2016 to 2019... Nice, no everything is working.

Sonntag, 2. Dezember 2018

{$IFDEF Version Problem

Inspired by Thomas Mueller (dummzeuch) here is a repost of my ifdef version maintained since 2015.

Since Day one of my FDK I had the same problem : How to maintain the distribution with different delphi versions.

The reason for writing my FDK is/was a better and faster development of iOS and Android apps, but after a short while it became more and more like a Swiss-knife for my daily work.

Since XE 2 all my new Programms are written with FMX and since 2015 I've used it every day for develop my Windows Programms, too.

So here is my version of the {$I}

unit Delphi.VersionConsts;

// =====>     FFFFFF  DDDDD   K    K    FireMonkey Development Kit
// ====>     F       D    D  K  KK        (c) F. Lauter   aka Mavarik
// ===>     FFFF    D    D  KKK               O. Münzberg aka Sir Rufo
// ==>     F       D    D  K  KK
// =>     F       DDDDD   K    K        Version 2015, 2016, 2017, 2018

 * Changes:
 * 01.10.15 Everybody should use this Unit - I hate the $I files
 *          I hope that someday EMBT will always provide this Unit with every
 *          new Version.


{$IF Declared(CompilerVersion)}
  CompilerVersion = 0.0;

  CompilerVersion_RIO     = 33.0;
  CompilerVersion_TOKYO   = 32.0;
  CompilerVersion_BERLIN  = 31.0;
  CompilerVersion_SEATTLE = 30.0;
  CompilerVersion_XE8     = 29.0;
  CompilerVersion_XE7     = 28.0;
  CompilerVersion_XE6     = 27.0;
  CompilerVersion_XE5     = 26.0;
  CompilerVersion_XE4     = 25.0;
  CompilerVersion_XE3     = 24.0;
  CompilerVersion_XE2     = 23.0;
  CompilerVersion_XE      = 22.0;
  CompilerVersion_2010    = 21.0;
  CompilerVersion_2009    = 20.0;
  CompilerVersion_2007    = 18.5;
  CompilerVersion_2006    = 18.0;
  CompilerVersion_2005    = 17.0;
  CompilerVersion_8_NET   = 16.0;
  CompilerVersion_7       = 15.0;
  CompilerVersion_6_2     = 14.2;
  CompilerVersion_6_1     = 14.1;
  CompilerVersion_6       = 14.0;

  Compiler_RIO     = CompilerVersion = CompilerVersion_RIO;   // Carnival
  Compiler_TOKYO   = CompilerVersion = CompilerVersion_TOKYO; // Godzilla
  Compiler_BERLIN  = CompilerVersion = CompilerVersion_BERLIN;
  Compiler_SEATTLE = CompilerVersion = CompilerVersion_SEATTLE;
  Compiler_XE8     = CompilerVersion = CompilerVersion_XE8;
  Compiler_XE7     = CompilerVersion = CompilerVersion_XE7;
  Compiler_XE6     = CompilerVersion = CompilerVersion_XE6;
  Compiler_XE5     = CompilerVersion = CompilerVersion_XE5;
  Compiler_XE4     = CompilerVersion = CompilerVersion_XE4;
  Compiler_XE3     = CompilerVersion = CompilerVersion_XE3;
  Compiler_XE2     = CompilerVersion = CompilerVersion_XE2;
  Compiler_XE      = CompilerVersion = CompilerVersion_XE;
  Compiler_2010    = CompilerVersion = CompilerVersion_2010;
  Compiler_2009    = CompilerVersion = CompilerVersion_2009;
  Compiler_2007    = CompilerVersion = CompilerVersion_2007;
  Compiler_2006    = CompilerVersion = CompilerVersion_2006;
  Compiler_2005    = CompilerVersion = CompilerVersion_2005;
  Compiler_8_NET   = CompilerVersion = CompilerVersion_8_NET;
  Compiler_7       = CompilerVersion = CompilerVersion_7;
  Compiler_6_2     = CompilerVersion = CompilerVersion_6_2;
  Compiler_6_1     = CompilerVersion = CompilerVersion_6_1;
  Compiler_6       = CompilerVersion = CompilerVersion_6;

  {$IF Compiler_RIO}    PackageVersion = '260';StudioVersion = '20.0'; {$ENDIF} 
  {$IF Compiler_TOKYO}  PackageVersion = '250';StudioVersion = '19.0'; {$ENDIF} 
  {$IF Compiler_BERLIN} PackageVersion = '240';StudioVersion = '18.0'; {$ENDIF}
  {$IF Compiler_SEATTLE}PackageVersion = '230';StudioVersion = '17.0'; {$ENDIF}
  {$IF Compiler_XE8}    PackageVersion = '220';StudioVersion = '16.0'; {$ENDIF}
  {$IF Compiler_XE7}    PackageVersion = '210';StudioVersion = '15.0'; {$ENDIF}
  {$IF Compiler_XE6}    PackageVersion = '200';StudioVersion = '14.0'; {$ENDIF}
  {$IF Compiler_XE5}    PackageVersion = '190';StudioVersion = '13.0'; {$ENDIF}
  {$IF Compiler_XE4}    PackageVersion = '180';StudioVersion = '12.0'; {$ENDIF}
  {$IF Compiler_XE3}    PackageVersion = '170';StudioVersion = '11.0'; {$ENDIF}
  {$IF Compiler_XE2}    PackageVersion = '160';StudioVersion = '10.0'; {$ENDIF}
  {$IF Compiler_XE}     PackageVersion = '150';StudioVersion = '9.0'; {$ENDIF}
  {$IF Compiler_2010}   PackageVersion = '140';StudioVersion = '8.0'; {$ENDIF}
  {$IF Compiler_2009}   PackageVersion = '120';StudioVersion = '7.0'; {$ENDIF}
  {$IF Compiler_2007}   PackageVersion = '110';StudioVersion = '6.0'; {$ENDIF}
  {$IF Compiler_2006}   PackageVersion = '100';StudioVersion = '5.0'; {$ENDIF}
  {$IF Compiler_2005}   PackageVersion =  '90';StudioVersion = '4.0'; {$ENDIF}
  {$IF Compiler_8_NET}  PackageVersion =  '80';StudioVersion = '3.0'; {$ENDIF}
  {$IF Compiler_7}      PackageVersion =  '70';StudioVersion = '2.0'; {$ENDIF}
  {$IF Compiler_6_2}    PackageVersion =  '60';StudioVersion = '1.2'; {$ENDIF}
  {$IF Compiler_6_1}    PackageVersion =  '60';StudioVersion = '1.1'; {$ENDIF}
  {$IF Compiler_6}      PackageVersion =  '60';StudioVersion = '1.0'; {$ENDIF}

  TDelphiVersion = Record
                     Name          : String;
                     StudioVersion : String;

  TDelphiVersions : array[0..6] of TDelphiVersion  = ((Name:'Delphi XE6';StudioVersion:'14.0'),
                                                      (Name:'Delphi XE7';StudioVersion:'15.0'),
                                                      (Name:'Delphi XE8';StudioVersion:'16.0'),
                                                      (Name:'Delphi 10 Seattle';StudioVersion:'17.0'),
                                                      (Name:'Delphi 10.1 Berlin';StudioVersion:'18.0'),
                                                      (Name:'Delphi 10.2 Tokyo';StudioVersion:'19.0'),
                                                      (Name:'Delphi 10.3 Rio';StudioVersion:'20.0'));

  Compiler_RIO_UP     = CompilerVersion >= CompilerVersion_RIO;
  Compiler_TOKYO_UP   = CompilerVersion >= CompilerVersion_TOKYO;
  Compiler_BERLIN_UP  = CompilerVersion >= CompilerVersion_BERLIN;
  Compiler_SEATTLE_UP = CompilerVersion >= CompilerVersion_SEATTLE;
  Compiler_XE8_UP     = CompilerVersion >= CompilerVersion_XE8;
  Compiler_XE7_UP     = CompilerVersion >= CompilerVersion_XE7;
  Compiler_XE6_UP     = CompilerVersion >= CompilerVersion_XE6;
  Compiler_XE5_UP     = CompilerVersion >= CompilerVersion_XE5;
  Compiler_XE4_UP     = CompilerVersion >= CompilerVersion_XE4;
  Compiler_XE3_UP     = CompilerVersion >= CompilerVersion_XE3;
  Compiler_XE2_UP     = CompilerVersion >= CompilerVersion_XE2;
  Compiler_XE_UP      = CompilerVersion >= CompilerVersion_XE;
  Compiler_2010_UP    = CompilerVersion >= CompilerVersion_2010;
  Compiler_2009_UP    = CompilerVersion >= CompilerVersion_2009;
  Compiler_2007_UP    = CompilerVersion >= CompilerVersion_2007;
  Compiler_2006_UP    = CompilerVersion >= CompilerVersion_2006;
  Compiler_2005_UP    = CompilerVersion >= CompilerVersion_2005;
  Compiler_8_NET_UP   = CompilerVersion >= CompilerVersion_8_NET;
  Compiler_7_UP       = CompilerVersion >= CompilerVersion_7;
  Compiler_6_2_UP     = CompilerVersion >= CompilerVersion_6_2;
  Compiler_6_1_UP     = CompilerVersion >= CompilerVersion_6_1;
  Compiler_6_UP       = CompilerVersion >= CompilerVersion_6;

  Compiler_RIO_DOWN     = CompilerVersion <= CompilerVersion_RIO;
  Compiler_TOKYO_DOWN   = CompilerVersion <= CompilerVersion_TOKYO;
  Compiler_BERLIN_DOWN  = CompilerVersion <= CompilerVersion_BERLIN;
  Compiler_SEATTLE_DOWN = CompilerVersion <= CompilerVersion_SEATTLE;
  Compiler_XE8_DOWN     = CompilerVersion <= CompilerVersion_XE8;
  Compiler_XE7_DOWN     = CompilerVersion <= CompilerVersion_XE7;
  Compiler_XE6_DOWN     = CompilerVersion <= CompilerVersion_XE6;
  Compiler_XE5_DOWN     = CompilerVersion <= CompilerVersion_XE5;
  Compiler_XE4_DOWN     = CompilerVersion <= CompilerVersion_XE4;
  Compiler_XE3_DOWN     = CompilerVersion <= CompilerVersion_XE3;
  Compiler_XE2_DOWN     = CompilerVersion <= CompilerVersion_XE2;
  Compiler_XE_DOWN      = CompilerVersion <= CompilerVersion_XE;
  Compiler_2010_DOWN    = CompilerVersion <= CompilerVersion_2010;
  Compiler_2009_DOWN    = CompilerVersion <= CompilerVersion_2009;
  Compiler_2007_DOWN    = CompilerVersion <= CompilerVersion_2007;
  Compiler_2006_DOWN    = CompilerVersion <= CompilerVersion_2006;
  Compiler_2005_DOWN    = CompilerVersion <= CompilerVersion_2005;
  Compiler_8_NET_DOWN   = CompilerVersion <= CompilerVersion_8_NET;
  Compiler_7_DOWN       = CompilerVersion <= CompilerVersion_7;
  Compiler_6_2_DOWN     = CompilerVersion <= CompilerVersion_6_2;
  Compiler_6_1_DOWN     = CompilerVersion <= CompilerVersion_6_1;
  Compiler_6_DOWN       = CompilerVersion <= CompilerVersion_6;


   Compiler = class sealed
         HasArc      = true;

  CompilerIsMobil     = true;

  PlattFormOSX        = false;
  SupportsAnsiStrings = false; //Compiler_BERLIN_UP;
{$DEFINE CompilerIsMobil}

   Compiler = class sealed
         HasArc      = false;

  PlattFormOSX        = true;

{$REGION 'Documentation'}
  /// <summary>
  ///   true if target ist iOS or Android
  /// </summary>
  CompilerIsMobil     = false;
  SupportsAnsiStrings = true;



Dienstag, 16. Oktober 2018

This was the beginning...Buy Books!

Looking back on my developments, I was always proud of my software. 

Starting with a programmable calculator and Eumel on a ELAN System in 1981, I got my first computer a Sharp MZ-700 in 1982.

First of all - With learning Basic from the MZ-700 manual, I was able to sell my first software - a graphic editor - to a local dealer to get my first floppy drive.

My first book: Rodnay Zaks programming the Z80. 

My next Computer was the MZ-800! With the MZ-800 graphics, 80 Chars per line was possible and with this, CP/M could be used for development. The Editor - of course - Wordstar, assembler M80, linker L80. At the same time: Learning UCSD-Pascal on Apple II in school and with Turbo-Pascal for CP/M at home.

I decided to write my second program in pure assembler:

The FLDOS  (Frank Lauter's Disk Operation System) 

Thanks to a very kind employee of  Motorola - I got a original manual of the floppy-controller-chip. But this journey is an other story. 

This "operating system" only take 1000 bytes of memory and was able to handle two parts. One for file-operation and the other was a Disk-Hex-Editor. Both parts could be reloaded from floppy without violating the 1000 bytes limit.

The Z80-Book "payed" the start of my development equipment. A dual floppy drive! One with 40 Tracks, one with 80 Tracks. ~2400 DM at this time.

After that I never tried to develop a whole program in pure assembler, because everything else was written in Turbo-Pascal. (Until today - I ignore my tries in C)

I have bought many books during these days, but nearly all books did not satisfy my needs. 700 or more Pages but only 60 pages of useful information - or less... So I had to buy 10 Books to get all the necessary information. (Perhaps that's why I stopped buying books)

And this leads to my next blog post ;-) or back to my Bookwriters post.

Mittwoch, 3. Oktober 2018

Pattern, naming and MVVM from a Delphi point of view.

Pattern are good tools and if you name it correct, other developer should know what you are talking about.

Name it singleton and every developer knows how to deal with it... (I hope). If you are talking about dependency injection, perhaps you are not using it, but at least you can google it. So giving things the right name is always a good thing.

So let's take a look at the development of a new FMX application. Of course, you want modern design, maintainable code and testable business-logic. I think you are looking for the MVVM pattern. Model, View, ViewModel, but after File, New, Multi-device Application you have a project and a Mainform and with a closer look into your tool palette, you are unable to find a View, a Model or even a ViewModel. Nothing to put on your form.

BTW.: Clicking non-visual controls on your form is the worst thing you can do with the goal of MVVM.

And now? What to do? Google for a library?. Perhaps you find something and using a library is normally better than coding all by your self. Do you remember the famous words?

Using is faster than coding!

There are many useful libraries out there! Some are free, some are expensive and some are necessary. I'll never start a new project without my FDK. ;-) Especially when dealing with FMX. So every new project! My last VCL project/tool was with XE. 

Perhaps you are already using my FDK, but unfortunately my MVVM-Plugin is still not available today. Why? Because I'm still dealing with different MVVM-pattern-designs.
After "MVVM was the start" in 2015, "MVVM or what I think it is" in 2016, trying attributes in 2017, "MVVM 2.0 I did it my way" in April 2018 and some kind of different Bindings (no blog post - let's call it MVVM 2.5) -  I'm still working on the "best" way to do it right. I thought I was "short on final" with my View to CRUD-DB Binding pattern, until last month "Sir Rufo" came up with a new binding approach. This with my last attempt will be the final one - I think - today... ;-)

At the moment I have 10 different Apps with my MVVM-Stuff from 2015,2016,2017 and 2018 and of course no MVVM-library form each year is compatible with the others. 4 different Libraries that are used in - at least - 2 Apps... Very bad to maintain. But back to Naming...

We are delphi developers and I never had a View. I have forms, frames - perhaps I use panels or layouts to do visual tricks. Or on iOS & Android I use TabControls to display different - ok call it views. But why name it View? It's a Form... Because a Form is not a View and a Form could display many View's, but this View's are Layouts or Frames? Confusing... Starting in 2015 I had no clue why a Form is not a View - now I understand, but perhaps I will change the names... ;-)

Let's do some examples.

You have:

- TPerson with Name, Day of birth and more
- TAddress with Street, Zip, Town...
- TCommunication with Fon, Mail and more
- TBank with Bankname and Account
- TInvoice with Invoice Fields
- TArticle with Title, Price, weight

In 99,9% all Visual-Controls on my Forms representing a Field in the Database. The Database has - of course - more fields like: Timestamp, CreationDate, LockOwner and more.

Perhaps my DB_Table (Person) is a TPerson,TAddress,TCommunication,TBank in one table. 80%

Perhaps I have TPerson, TAddress1, TAddress2, TFon1, TFon2, TBank1, TBank2, TBank3 100% until 2010 in one record written as binary to file with index. *lol*

Today I would have one DB-Table for TPerson, one for TAddress, one for TCommuniction  and one for TBank.

For this example I like to display this kind of data direct in Form-Fields (TEdit,TCombobox, TCheckBox etc.) and not in a Grid. Perhaps at the bottom a grid with the TInvoice and on dblclick display the Invoice on a second form with the containing TArticle(s).

So what do we have?

A FORM with Field separated in 5 subFRAMES, each subframe depends on a Databasetable. To Display more than one TAddress I had to create the controls at runtime on a scollbox. 
F for Form or Frame and D for Database.

Rule of thumb: Never use visual controls to keep/store your Data. 

That's why we need some class/Memory for our Fields. (That's why I would never use a Live-DB-Connection, where Edit's could only display the data if you have a open connection.) ARC on iOS & Android and no ARC on Windows - so wie need an InterfacedObject as Memory - representation of our Form / Table. One last thing is something to Control the Dataflow from From (TEdit) to Memory -> to Database. Do we have a new Name?

FCMD - Form,Controller,Memory,Database.

FCMD - Sound like my new MVVM... ;-) or is it MVC?

Btw: What was the reason to do MVVM? Ahh yes... to have a good layer separation and testable units with - at the best - no dependencies.

Because we all doing unit-test... ;-)

What is your unit test coverage? 3%, 30%, 70% more?

OK - assume 100% coverage of you business units. Do you have unit tests for your ViewModel?
Maybe some time we got an IDE that could display the unit test coverage.

Back to the Form - we have a grid with a list of invoices - the grid has it's own model inside. Each row could be a Viewmodel - InterfacedObject as Memory-Representation of the Database. So we need a Grid-Controller for displaying the TArray<IInvoice> perhaps with paging and background thread reloading new rows... Again a Controller to move n rows from Memory to the Grid-(View)Model.
And an Model "InterfacedObject Multiloader" to load all Invoices with
"Select * from InvoiceDB where Person = Person.GUID". 

On a DBLClick we could create a new Form and inject the IInvoice from GridHelper(ARow).GetRow<IInvoice> into the Controller of the new Form. Bingo... Working Demo.

So why FCMD and not MVVM or is FCMD another name for MVC?

To answer the question we have to look at the bindings.

In MVC the user uses the Controller to manipulate the Model that updates the View.
In MVVM then View has a bidirectional binding to the ViewModel and the ViewModel has a bidirectional binding to the Model. Is the Model the Database - no. Where is the data stored? In the ViewModel or in the Model? Ask 10 developer and you got 10 different answers. My ViewModels also have the Data and the Model has "all-Data" or the Interface for storing the Data via DB or REST or SOAP or Whatever.
In FCMD you have the Form and the RAD-approach. With the assignment of the ViewModel Memory-Representation to the View Form, you have to create your bindings to the Memory-Representation. This creates the Bind-Controller. If you type something into a TEdit the Bind-Controller transfers the data direct into your Model TInterfacedObject. This Object fires a propertychanged over the viewmodel  Bind-Controller direct to the view - Form. If you reload  or load a different data, your View Form also displays the new content. The Memory Object has all Fields of the TableRow with some Attributes to Create and Handle the Database with CRUD I/O.
Perhaps we change from C for Controller to B for Binding.
So with FBMDR you could write your data to a local DB and in Background over REST to a Server. That's why my CRUD/ORM Memory-Representation could transfer all field to and from JSON.
Btw. Form-Bind-Memory-Database sound like an inmemory database. So change the D to C like in CRUDTable

At this point no unit tests for the View Form-Logic, because there is no ViewModel. Perhaps parts of the ViewModel stuff is in the MemoryObject like "CanSave/CanDelete/Modified" and so on. The other things could be sent via the Bind-Controller or with TMessages into the View Form. Like DisableButton. 

With FBMC(R) 99,9% is testable like in MVVM but 70% less source code to type and 60% less Units.

I would love to see FBMC spread all over the Delphi-Community and beyond,

#FBMC - Form-Bind(ed)-Memory-CRUDTable

or perhaps I do some modifications and call it MVVM 2.5 - again...

The Binding-Controller not only handles the transfer, "he" is also able to do some data conversion, like string to integer or String to TDatetime or A->B, B->A.

So far so good. But with our new FMX-App we need more. Nearly every FMX-Control has some animations build into the style. This animation are handled with timers - of course in the main thread. Like MouseDown, MouseOver, MouseUp and so on. If you are new to FMX you would write:

Procedure TButton1Click(Sender : TObject)
  Button1.Enabled := false;
  Button1.Enabled := true;

If you click on Button1 the button is still enabled- you get no MouseDown animation your UI has no response and after 20Sec you can - perhaps - see MouseDown, MouseUp. But things are getting worse.

Procedure TButton1Click(Sender : TObject)
  Button1.Enabled := false;
  NewForm := TNewForm.Create(Application);

If you free the Form that contains the Button1 in the TNewForm Show or Create Event. You'll get an exception, because the MouseUp-Animation will be displayed after the TNewForm.Create is called on a no more assigned Button1.

Nearly everything you want to do like the good old RAD-Days you have to do asynchronous. At best, in the onIdle-Event for UI-Stuff or in Thread with other things.

Procedure TButton1Click(Sender : TObject)
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

    begin // Thread Context
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 


Procedure TButton1Click(Sender : TObject)
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

    begin // UI Thread 
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 

With the Bind-Controller from #FBMC you could bind a command/Method at the Button that can handle this kind of action without the need of writing additional lines of code.

With all these and the additional JSON Import/Export, REST Client/Server Unit you are able to Build your Multi-Device-App with "Cloud-Dataexchange" in minutes. At the moment it works perfect with my Windows-Server. 

Next step is to test the Serverside in a Linux-Docker-Container...

But every Day is too short... So stay tuned...