MVVM Starter Kit (Part 1 of 3)
January 22, 2018 Erik van Bilsen
January 22, 2018 Erik van Bilsen
[SHOWTOGROUPS=4,20]
Wikipedia: Для просмотра ссылки Войдиили Зарегистрируйся
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 Для просмотра ссылки Войдиили Зарегистрируйся we will focus on the ViewModels and how these can be unit tested. In the Для просмотра ссылки Войди или Зарегистрируйся 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 Для просмотра ссылки Войдиили Зарегистрируйся. This repository depends on our Для просмотра ссылки Войди или Зарегистрируйся 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:
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.
For another introduction of MVVM, take a look at Malcolm Groves’ excellent CodeRage video Для просмотра ссылки Войдиили Зарегистрируйся. Malcolm has is Для просмотра ссылки Войди или Зарегистрируйся 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:
Why this MVVM Starter Kit?
There are some other MVVM solutions out there, such as Для просмотра ссылки Войдиили Зарегистрируйся. 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 Для просмотра ссылки Войдиили Зарегистрируйся.
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:
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:
You will use this pattern all the time:
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:
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 Для просмотра ссылки Войдиили Зарегистрируйся of this series.
The following image shows most data bindings for the Album view:
[/SHOWTOGROUPS]
Wikipedia: Для просмотра ссылки Войди
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 Для просмотра ссылки Войди
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 Для просмотра ссылки Войди
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:
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.
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.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).
For another introduction of MVVM, take a look at Malcolm Groves’ excellent CodeRage video Для просмотра ссылки Войди
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.
Why this MVVM Starter Kit?
There are some other MVVM solutions out there, such as Для просмотра ссылки Войди
You can also build your own MVVM solution on top of Delphi’s LiveBindings, as Malcolm Groves shows in his Для просмотра ссылки Войди
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.
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:
1 2 3 4 5 6 7 8 | procedure TAlbum.SetRecordLabel(const Value: String); begin if (Value <> FRecordLabel) then begin FRecordLabel := Value; PropertyChanged('RecordLabel'); end; end; |
- 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.
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:
1 2 3 4 5 | procedure TViewAlbum.SetupView; begin ... Binder.Bind(ViewModel.Album, 'RecordLabel', EditRecordLabel, 'Text'); end; |
“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 Для просмотра ссылки Войди
The following image shows most data bindings for the Album view:
[/SHOWTOGROUPS]
Последнее редактирование: