Libraries · Patterns · Uncategorized

MVVM Starter Kit (Part 1 of 3)

We present a framework to help you get started separating user interface from code using the Model-View-ViewModel pattern.

In this first part, I will briefly introduce the MVVM pattern, talk about data binding and start building out the Models for a sample application.

In part 2 we will focus on the ViewModels and how these can be unit tested. In the last part we look at the Views and how to bind them to ViewModels. It also shows how the same ViewModels can be used to build a VCL version and FireMonkey version of the same application, with a minimum of framework-specific code.

TL;DR

But first a general note. I know my articles are a bit (T)L, so some people DR them. In this world of bite-size snacks, I like to provide (hopefully satisfying) meals. But I understand if you only have time for a quick snack. I will try to add plenty of (sub)headings to make it easier for you to skim the text. You can always come back later for the details. If there are other things I can do to improve the experience, let me know in the comments. I will try to be accommodating.

That being said, these three articles are bit long, not because it is a terribly complicated subject, but because there is a lot to talk about. You can think of it as part introduction to MVVM and part a more detailed programming guide.

Source Code and Sample Project

As usual, you can find the code for these articles on GitHub in the repository MvvmStarterKit. This repository depends on our GrijjyFoundation repository, so be sure to pull the latest version of that repo as well. Make sure that GrijjyFoundation is in your library path (or otherwise at the same directory level as MvvmStarterKit).

We discuss the MVVM pattern using a sample application called MyTunes, which is part of the repository. This is a simple album management app that you can use to manage albums and songs. There is both a VCL and FireMonkey version of this application. They share the same Models and ViewModels, and only have different Views (forms). I suggest you open a version of this app in Delphi, since these articles regularly refer to it.

The MVVM Pattern

Model-View-ViewModel is one of several patterns that can be used to separate the user interface of your app from the business logic. It is similar to other patterns like Model-View-Controller (MVC) and Model-View-Presenter (MVP), but imho it is better in reducing the dependencies between its separate parts: a Model has no knowledge (or dependency) on the ViewModel, and the ViewModel has no knowledge (or dependency) on the View. All dependencies go in only 1 direction: from left to right in the following diagram:

Mvvm

Only the View part deals with VCL or FireMonkey controls. It binds these controls to properties of a ViewModel. The ViewModel is really just a “model of the view”. It knows nothing about the actual user interface but it does contain the logic to operate the user interface. The View uses Actions (aka commands) to trigger this logic. The ViewModel gets its data from the Model and the Model contains all logic related to this data.

In short: the Model contains data and business logic, the ViewModel contains user interface logic and the View contains UI elements (and preferable no logic at all).

Of course, the Model must still be able to present its data to the View, but it does so without directly talking to the View. Instead, it uses data binding the bind the two together to create a loose coupling. Data binding is at the heart at the MVVM pattern, and is the subject of most of this first article in the series.

For another introduction of MVVM, take a look at Malcolm Groves’ excellent CodeRage video Introduction to MVVM in Delphi. Malcolm has is list of other MVVM resources as well.

Why Model-View-Whatever?

A RAD environment like Delphi makes it very easy to get into bad habits and mix code and UI together. I do it all the time in research projects and prototypes. But when it is time to build a production-quality app, separating UI from code has many benefits:

  • It greatly improves the testability of your app. User interfaces are notoriously hard to test. By making sure the user interface layer contains only a minimum amount of code, you can achieve much higher test coverage, resulting in fewer bugs and lower maintenance costs.
  • It makes it easier to develop different user interfaces for different platforms or purposes. Because the UI contains almost no code, it takes less time to create both a VCL and FMX version of your app if you want to. Or create a different desktop and mobile experience of the same app. Or create a different OEM or white label version. These scenarios are much less expensive to develop with a Model-View-Whatever pattern.

Whether you want to use MVC, MVP, MVVM or another pattern to accomplish this is a matter of personal preference and available tools. It doesn’t matter much which pattern you pick, as long as you pick one.

Why this MVVM Starter Kit?

There are some other MVVM solutions out there, such as Stefan Glienke’s DSharp framework. That framework is geared towards VCL applications however, while we at Grijjy develop mostly FireMonkey applications.

You can also build your own MVVM solution on top of Delphi’s LiveBindings, as Malcolm Groves shows in his video.

One of the reasons we chose to create our own framework is that both LiveBindings and the data binding in DSharp can be a bit slow. They offer great flexibility through data binding expressions, but that comes at a run-time cost. Since we develop highly responsive applications, this can become problematic at times. In fact, this is one of the most common complaints about the MVVM pattern. Our data binding solution is less flexible, but more performant and arguably somewhat easier to use. If you need more complicated data binding expressions, then you can write them in Delphi code as property getters/setters of a ViewModel.

Of course, everyone has different requirements, so you usually end up creating your own MVVM solution anyway. Therefore, as the name of this post implies, this MVVM Starter Kit can be used as a base for your own solution. It already offers a lot of functionality out of the box, but you can complement it with your own additions.

Enforcing Separation

It requires some discipline to keep user interface and code really separated. You can use the following naming conventions and directory organization to help enforce the rules:

  • Create a separate directory for your Models. Start each unit in this directory with a “Model.” prefix, such as Model.Album in the MyTunes app.
  • The units in the Models directory may only “use” other Model units or RTL units. They may never use VCL, FMX, ViewModel or View units. (For this discussion, I use the term RTL units for all units that are not VCL or FMX units).
  • Also use a separate directory for your ViewModels. Start each unit in this directory with a “ViewModel.” prefix (eg. ViewModel.Album).
  • The units in the ViewModels directory may only “use” ViewModel, Model and RTL units. These units also may never use VCL, FMX or View units.
  • Finally, create a separate directory for your Views (such as forms or frames). Again, start each unit here with a “View.” prefix (eg. View.Album).
  • The units in the Views directory are the only ones that may “use” VCL or FMX units. It may also use ViewModel and Model units.

By using these rules, it will be easier to maintain a good separation of UI and code. You can take this even further by putting all Models in a separate run-time package and all ViewModels in another run-time package. If you make sure the Models package does not “require” any ViewModel, VCL or FMX packages, then you can’t even break the rules. Unfortunately, run-time packages come with their own (cross-platform deployment) issues, so this may not be worth it.

Data Binding

Data binding is at the heart of the MVVM pattern. You can bind properties of different objects together so that changes to a property of one object are automatically propagated to a property of another object. This can save a lot of code, especially if you are used to manually synchronizing the user interface with the underlying data.

Delphi provides a very powerful and flexible LiveBindings framework that does just this. But as I mentioned before, we use something a bit more light-weight. It is based somewhat on the data binding framework that Microsoft uses for their WPF, Silverlight and Xamarin GUI frameworks. This is not too surprising since Microsoft developed the MVVM pattern and they use it for all their modern (non-web) user interfaces. Some of the class and interface names we use are directly derived from Microsoft’s counterparts, although the implementation is completely different.

Observable Objects

To be useful for data binding, an object that is the source of a binding must notify any interested parties whenever one of its properties has changed. In other words, it must be “observable”. For our purposes, an object is observable if it implements the IgoNotifyPropertyChanged interface. Other objects use this interface to subscribe to notifications of property changes. You usually don’t need to implement this interface yourself. Instead, you can use a base class, such as TgoObservable, that already implements this interface.

Take a look at the TAlbum class (in the unit Model.Album) for an example. This class is derived from TgoObservable, so that other parties can listen for changes to its properties. To enable this, it should call PropertyChanged whenever one of its bindable (or observable) properties has changed. The setter for the RecordLabel property is a simple example:

procedure TAlbum.SetRecordLabel(const Value: String);
begin
  if (Value <> FRecordLabel) then
  begin
    FRecordLabel := Value;
    PropertyChanged('RecordLabel');
  end;
end;

You will use this pattern all the time:

  • First check if the value is different from the current value (to avoid firing notifications if the value hasn’t actually changed).
  • Then change the backing field accordingly.
  • And finally fire the notification by calling PropertyChanged with the name of the property. This string is case-sensitive.

Binding Properties

Now, other objects can bind to the RecordLabel property of an album.

For example, the album view (the UI form used to edit an album) binds this property to the Text property of a TEdit:

procedure TViewAlbum.SetupView;
begin
  ...
  Binder.Bind(ViewModel.Album, 'RecordLabel', EditRecordLabel, 'Text');
end;

It uses the TgoDataBinder.Bind method to create the binding. You should read this statement as:

“Bind ViewModel.Album.RecordLabel to EditRecordLabel.Text”

Whenever the RecordLabel property of the album changes, the Text property of the edit box is updated accordingly. By default, bindings are bidirectional. This means that when the Text property of the edit box is modified by the user, the the Album.RecordLabel property is updated as well. This only works if the TEdit control implements the IgoNotifyPropertyChanged interface as well. We will look into this in the 3rd part of this series.

The following image shows most data bindings for the Album view:

Mvvm-BindingProperties

Binding Sub-Properties

You can bind to sub-properties by providing a “property path” instead of a simple property name. A property path is a string of property names separated by periods. For example, the Albums (plural) view shows a list of albums. But for the selected album, it also shows the title (among other things). To bind this title to a text control, you use the property path “SelectedAlbum.Title”:

procedure TViewAlbums.SetupView;
begin
  ...
  Binder.Bind(ViewModel, 'SelectedAlbum.Title', TextTitle, 'Text',
    TgoBindDirection.OneWay);
end;

This image shows to other bindings for the Albums view:

Mvvm-BindingSubProperties

As you can see, you can also bind to properties like FontColor and Bitmap:

procedure TViewAlbums.SetupView;
begin
  ...
  Binder.Bind(ViewModel, 'SelectedAlbum.TextColor1',
    TextTitle.TextSettings, 'FontColor', TgoBindDirection.OneWay);
  Binder.Bind(ViewModel, 'SelectedAlbum.Bitmap', ImageAlbumCover, 'Bitmap',
    TgoBindDirection.OneWay);
end;

Note that you can only bind to sub-properties if all properties of the path, except for the last one, are of a class type. You cannot (currently) use properties of record or interface types, although I may add support for this in the future.

Properties vs Sub-Properties

There is a fundamental difference between binding to properties and binding to sub-properties. For example, there is a difference between

Binder.Bind(ViewModel.Foo, 'Bar', EditBox, 'Text');

and

Binder.Bind(ViewModel, 'Foo.Bar', EditBox, 'Text');

In the first example, you are binding only to the current value of ViewModel.Foo. If ViewModel.Foo is set to a different value, then the EditBox is still bound to the original value. This model is used in the first screenshot above: this dialog box is used to edit a single album, making this the preferred way to bind the properties in this case.

In the second example, whenever the value of ViewModel.Foo changes, the EditBox binds to this new Foo instance. This model is used in the second screenshot above: if the user selects a different album in the list, then the controls on the right are updated accordingly. Use this kind of binding when the object you are binding to can change at run-time. If that is not the case, then it is more efficient to use the first kind of binding instead.

Binding Direction

The TgoDataBinder.Bind method has a number of optional parameters. The first one is a binding direction, which defaults to TgoBindDirection.TwoWay, as we saw before. You can also bind in a single direction:

procedure TViewAlbum.SetupView;
begin
  ...
  Binder.Bind(ViewModel.Album, 'IsValid', ButtonOK, 'Enabled',
    TgoBindDirection.OneWay);
end;

This enables the OK button only when the TAlbum.IsValid property is True. In this case it makes no sense to bind in the other direction. And since the IsValid property is read-only, that would not work anyway (an exception is raised if you try to bind to a read-only property).

BTW: TAlbum.IsValid is a computed property, which returns True if at least a title and artist is specified. This means that the setter methods for the Title and Artist properties must also fire a notification for the IsValid property:

procedure TAlbum.SetTitle(const Value: String);
begin
  if (Value <> FTitle) then
  begin
    FTitle := Value;
    PropertyChanged('Title');
    PropertyChanged('IsValid');
  end;
end;

Binding Flags

The next optional parameter is a set of binding flags:

  • TgoBindFlag.TargetTracking: set this flag to update the source while the target property is changing. For example, when using a TEdit control, a notification is only send after the user has finished editing the text (by exiting to control for example). By setting this flag, a notification will be fired for each character the user enters in the control. For this to work, the target must implement the IgoNotifyPropertyChangeTracking interface.
  • TgoBindFlag.SourceTracking: works the same, but applies to the source instead of the target.
  • TgoBindFlag.DontApply: normally, when creating a binding, the initial value of the source property will be applied to given target property. If you don’t want this, then you can specify this flag. A reason you want to do this is if you have a one-way binding from the View to the ViewModel, but you don’t want to update the ViewModel with the current value in the View.

Our album example uses the TgoBindFlag.TargetTracking flag to update the Album.Title property for every character the user enters in the EditTitle edit box:

procedure TViewAlbum.SetupView;
begin
  ...
  Binder.Bind(ViewModel.Album, 'Title', EditTitle, 'Text',
    TgoBindDirection.TwoWay, [TgoBindFlag.TargetTracking]);
end;

Value Converter

The final optional parameter is a value converter. You can supply a converter to convert a source property value before assigning it to a target property. You can even change the data type if needed.

The data binder already takes care of a lot of “trivial” conversions. For example, you can bind an integer property to a floating-point property, then the value will be converted to floating-point on assignment. The other way around, the fractional part of a floating-point value will be ignored (truncated) when assigned to a integer property. Likewise, the binder automatically converts numbers to and from strings. It even converts from object to Boolean, where the value is True if the object is assigned, or False otherwise. This is useful for example to set the Enabled property of a button, depending on whether an object is assigned or not.

If the built-in conversions don’t suffice, you can pass a converter class. Our album example uses a (contrived) converter to convert the album title to a form caption:

procedure TViewAlbum.SetupView;
begin
  ...
  Binder.Bind(ViewModel.Album, 'Title', Self, 'Caption',
    TgoBindDirection.OneWay, [], TTitleToCaption);
end;

The converter class (TTitleToCaption in this example) consists of just two virtual class methods ConvertSourceToTarget and ConvertTargetToSource. The first one must be overridden. The second one is optional and only used for two-way bindings.

Note that you don’t create an instance of the converter class. Instead, it uses Delphi’s (unique) language feature of virtual class methods.

Our sample converter just prefixes the title with the text “Album: “:

type
  { Prefix an album title with the text 'Album: ' }
  TTitleToCaption = class(TgoValueConverter)
  public
    class function ConvertSourceToTarget(
      const ASource: TgoValue): TgoValue; override;
  end;

class function TTitleToCaption.ConvertSourceToTarget(
  const ASource: TgoValue): TgoValue;
begin
  Assert(ASource.ValueType = TgoValueType.Str);
  Result := 'Album: ' + ASource.AsString;
end;

All values are encapsulated in a TgoValue type, so it can be used for integers, floating-point values, strings and some other types. The type of the source does not have to be the same as the type of the result. TgoValue is a very light-weight version of Delphi’s TValue type. See the unit Grijjy.Mvvm.Rtti for more information on how to use it.

This example also shows that the Album.Title property is bound to multiple targets (the caption of the form and the Text property of an edit box). This is perfectly legal. Be careful though with two-way bindings, since you can end up with multiple sources fighting to update a property of a target.

Binding Collections

The data binder is also used to bind entire collections to list-like views (such as TListBox or TListView). We leave the discussion of this to the 3rd (and final) part of this series.

Limitations

The TgoDataBinder class has some limitations compared to Delphi’s LiveBindings. Most notable, it does not support binding expressions: you can only bind one property to another.

As said before, you can bind to sub-properties however. Technically, these are also called expressions, but not the kind of expressions I mean here.

Fortunately, you can easily write your expressions in regular Delphi code as properties of a ViewModel. For example, a TAlbumTrack has a Duration property of type TTimeSpan. You cannot bind using an expression like 'Duration.Minutes', since TTimeSpan is record. But you can easily write a property that returns this value, as the TViewModelTracks class does:

type
  TViewModelTracks = class(TgoObservable)
    ...
    property SelectedTrackDurationMinutes: Integer
      read GetSelectedTrackDurationMinutes
      write SetSelectedTrackDurationMinutes;
  end;

function TViewModelTracks.GetSelectedTrackDurationMinutes: Integer;
begin
  if Assigned(FSelectedTrack) then
    Result := FSelectedTrack.Duration.Minutes
  else
    Result := 0;
end;

Sometimes, you can achieve the same with a value converter. Choose what is most appropriate in your situation.

MyTunes Models

We conclude this first part with a quick tour of the models in the MyTunes sample application.

Mvvm-M

The MVVM pattern (like other MV* patterns) does not say much about the Model part. This is entirely application specific and mostly regarded as a black box containing your data and business logic. How you organize this part is entirely up to you: it may get the data from a local database, custom backend, REST API or by some other means. Or it may not use a “traditional” data source at all. For example, if you are developing an image editing application, your model may contain image data coming from a local image file.

MyTunes Sample Data

Since this mini series focuses on the MVVM pattern and not on data access, we keep things simple. The application does not use a database or REST server. Instead, it uses a small set of sample data that is embedded into the executable as a resource. I created this sample data by using the Apple Music API to query the most popular albums in the US at the time of writing. The results are saved to a Google Protocol Buffer file (using our Google Protocol Buffer engine) and converted to a resource file. This makes it easy to load the sample data into memory with only a couple of lines of code.

See the Data.pas unit for a description of the serialization format and the code to load the data set.

The sample application does not provide a way to save any changes back to the “database”, since that is outside the scope of these articles.

Album and Track Models

The sample application has just two main entities: an album and a track. These have corresponding models called TAlbum and TAlbumTrack. These models are super simple. They just derive from TgoObservable and add bindable properties that use the PropertyChanged pattern we discussed earlier in this article.

In addition, the TAlbum model contains some simple “business logic” in the form of methods for adding and removing tracks.

This is all pretty straight-forward. Take a look at the code if you want more details.

Working with Bitmaps

The only property that warrants some more explanation is TAlbum.Bitmap. Every album has a cover image. In the “database”, this image is stored in raw JPEG format (through the RawImage property). However, to display the image, it must be converted to a bitmap. Delphi does not provide a framework-independent representation of a bitmap. Instead, it has separate TBitmap classes for the VCL and FMX frameworks. Of course, you cannot uses VCL or FMX dependencies in your models!

So TAlbum.Bitmap is declared as a TObject. The property getter uses the helper class TgoBitmap to load the raw (JPEG) data into a VCL or FMX bitmap, depending on framework:

function TAlbum.GetBitmap: TObject;
begin
  if (FBitmap = nil) and (FRawImage <> nil) then
  begin
    FBitmap := TgoBitmap.Load(FRawImage);

    { Don't need raw image anymore. Release it. }
    FRawImage := nil;
  end;
  Result := FBitmap;
end;

This is an example of a “lazy” property: the bitmap is only loaded the first time the property is accessed.

You may wonder if using TgoBitmap creates an implicit dependency on VCL or FMX. It does not. It uses a variable of a procedural type that is used to actually load the bitmap. The VCL and FMX specific MVVM units (which we get to in part 3) , set this variable to a framework-dependent bitmap loading routine.

Master Model

You usually have some sort of entry point to access the models in your application. These models could be global variables, so you can access the everywhere in the application. To keep things clean and OOP, I chose to create a “master” model, simply called TModel. This is a singleton object that gives you access to the models in your application:

type
  TModel = class
  public
    ...
    class property Instance: TModel read GetInstance;

    property Albums: TAlbums read FAlbums;
  end;

In this example, it contains just a single property Albums, since the tracks our owned by their respective albums.

This class uses a singleton pattern, where you access the singleton through its Instance class property:

for Album in TModel.Instance.Albums do
  ...

The TModel class also contains some (private) business logic to load the sample data set and convert it to TAlbum and TAlbumTrack models.

Note that this is just one way to organize your models. The MVVM pattern doesn’t prescribe how you should do this. I chose to derive the models from TgoObservable so we can use data binding to bind to its properties. However, if you don’t want to do this (or you cannot do this), then you can still use the MVVM pattern. In that case, you will have to make bindable “property wrappers” in your ViewModels. These property wrappers than access the corresponding (non-bindable) properties of the underlying Model. You can argue that this is an even cleaner approach since it doesn’t pose any requirements on the Models. But it results in more code in your ViewModels (although not in your Views).

Next Time

That’s it for now. The next two parts will be a (little) bit shorter.

We’ll take a look at the ViewModels in the next article, and how you can unit test ViewModels by simulating user actions.

17 thoughts on “MVVM Starter Kit (Part 1 of 3)

  1. Hi

    I am very impressed with your article and am considering using your MVVM starter kit. I especially like the clarity of your methods for creating bindings in code.

    My hesitation is that the bindings in the kit do not support interfaces. I am wondering why that is not important to you. I was planning to use interfaces for access to models and view models.

    I admit that I am not yet sold on the utility of interfaces other than their ease of memory management(the only biggie for me). For my purposes, pluggable implementations can be achieved through wrapper classes as well as interfaces. But this may be because I have not yet started using intefaces and so I don’t know what other benefits they may bring. I do know that “everybody” says they’re good and should be used.

    I know you put a log of thought into the starter kit and I wondered why it wasn’t important to support interfaces in its initial version.

    Thanks

    Like

    1. Thanks for liking the articles!

      There are a couple of reasons I haven’t added support for binding to interfaces:
      * We currently don’t need it in our own code. We are not a component vendor and just share some of our code with the public. So most of the code we share is stuff we actually use. Also, it is a “starter kit” meaning that you will probably need to add your own code anyway to make it fit your needs.
      * I would require quite a bit of extra work, code and testing.
      * The bindings depend on RTTI and Delphi’s RTTI for interfaces is less complete than RTTI for classes. This means that some bindings may currently not be possible when using interfaces.

      I must confess that sometimes I use interfaces is for memory management only as well. You might say that this is “abusing” the feature, and interfaces are primarily intended to define contracts. But it is a very useful for memory management…

      Like

    1. I did take some precautions to prevent infinite loops, by only firing binding notifications if a value has actually changed. This isn’t a 100% fool proof in case of long dependency chains though. A more robust solution could use something like a loop counter to detect this situation. Let me know if you run into any issues there…

      Like

  2. A question about binding and lists. I have a Grijjy-TListView bound to a TEnumerable property in the Viewmodel. The Viewmodel property is read-only. There is no setter method for the property and no AddItem action for the UI. The list comes from a database. Any time a new item arrives it is inserted into the database. I would like for the TListView to update whenever that occurs.

    The TListView is bound to the Viewmodel because the database gives me a TList and I have to convert that to a TgoObservableCollection for binding. In case it’s important, I modified the code for TgoObservableCollection and made the FList field protected. In my derived class I can simply assign the database TList to FList to make the conversion.

    This works for the initial read of the database at app launch. But as far as I can tell, it is not enough to have the Viewmodel simply call PropertyChanged(‘PropertyName’) whenever the database is updated. The FOnPropertyChanged event is not assigned for this property in the Viewmodel.

    So I’d like to ask for your recommendation before I start trying to figure out how to make this work.

    Thanks

    Like

  3. By assigning the list from the database directly to the FList property, you are bypassing most methods of TgoObservableCollection that take care of firing property change notifications.

    You may be able to fix this by setting the OnNotify event of the list from the database. In the handler of this event, you would call DoCollectionChanged and/or DoPropertyChanged(‘Count’), depending on the type of notification. You can take a look at TgoObservableCollection.Add/Delete etc. for examples on how to do this. You probably want to add this event handler to the TgoObservableCollection class, so you can access these private methods.

    I don’t know your database model, but if you have written it yourself, then you could also have it return a TgoObservableCollection instead of a TList. (It doesn’t have to be a TgoObservableCollection; It can be any class that derives from TEnumerable and implements the IgoNotifyPropertyChanged and IgoNotifyCollectionChanged interfaces. So you could create your own database-specific observable collection class if you want to).

    Like

    1. Thank you for taking the time to respond. Your starter kit is saving me a lot of time and a lot of code. I appreciate that. It has gone in so well and is working so smoothly that I got carried away when I touched your code. My apologies.

      Like

  4. Hi Erik
    18 months after this post… 🙂
    I’ve been using this code for a small project and really like it (thanks). I’m curious if you have been keeping the github repos in sync with your own changes. I’ve seen sporadic commits with more activity with the Grijjy Foundation, but little here (and not recently).

    The reason I ask is that I’ve attempted my own addition to add things like TFrame (FMX) with limited success and I’d rather ensure I was working with the latest code if available rather than try to debug with my limited knowledge of the framework.

    Thanks

    Like

    1. Hi Ian,
      Glad you like it!
      The MVVM starter kit was always a fork of our own MVVM code base, hence the name “starter kit”. The idea is that can use at as a base for your own framework, and that you add your own extensions.
      So, we haven’t been updating the repository other than bug fixes.
      So yes, you are working with the latest version of the code…

      Like

      1. Thanks for your reply and confirmation of the latest source.
        If you do add your own support for TFrame then I’d prefer to adopt your code over mine. 🙂
        The main issue I have (with TFrame) is that the IDE inserts extra properties at the top of the fmx file (thinking it’s a TForm) which causes a runtime error.
        I need to manually edit the fmx before compiling after any unrelated unit changes in the IDE. I’m not sure if that is a problem in my added code or an issue with the IDE not identifying the class properly.

        Liked by 1 person

  5. Ian, I haven’t looked at support for TFrame’s. Maybe you need to write a custom expert for this and install it into an IDE package to make it work. I don’t know the exact problem you are facing. If you have a small sample project that shows the issue, then you can send it to me (to erik-at-grijjy-dot-com) so I can take a look at it.

    Like

    1. Thanks for that. I’ll try to trouble-shoot it a bit more myself and will follow up if need be. (It’s not a show stopper since I can manually edit the fmx file.)

      Like

  6. Now in 2022, is there any update on the MVVM kit? I meant – any code enhancements (if any) has been merged or added to this public repo?

    Like

    1. No updates to the kit. It is a starter kit so you can make your own enhancements 😉. There may be some forks w
      From other people with enhancements though…

      Like

Leave a comment