I want to dig a little into the formatting support in LiveBindings. How can we control how our data is displayed when we bind it to UI elements?
If we go back to the traditional data binding support in VCL, different TField descendants exposed different formatting properties. For example, TNumericField exposes a DisplayFormat property where you can specify a format string to be used when displaying the contents. TField also exposes an OnGetText event where you can do whatever you like to the value before it is displayed in the UI.
The good news is that if you are binding from a TDataset using a TBindSourceDB, all of these will be respected by LiveBindings.
But what if you’re not binding from a TDataset? Let’s try it out and see.
Note, you can do this either by dropping the components onto the form and wiring them together using the LiveBindings Designer, or right click on the fields in the TPrototypeBindSource in the LiveBindings Designer and select Link to New Control.
Still in the LiveBindings designer, select the arrow that runs between the Lastname field in the PrototypeBindSource and the Edit box. This will select the aptly named TLinkControltoField instance that LiveBindings is using to bind the Edit Control to the Lastname field. It has a CustomFormat property, where we can enter an expression that will be used to format our display. Within this expression, %s represents the underlying field value, and importantly, we can call methods within the expression we enter. In this example I’ve entered “Mr ” + UpperCase(%s) as the value, and given I used ColorNames as the generator for this field, I end up with what looks like a draft list of characters from Reservoir Dogs.
An important question is where are these methods coming from? Select the BindingsList that was added to your form when you started wiring up controls and notice it has a Methods property. In here we can see all the methods currently available to your expressions. Be aware though, that these method names are case sensitive.
These standard methods are a good start, but what if we want to do something in our expression that is not already covered by one of the standard methods? Well, write a custom one of course!
In the sample code for this article you’ll find a package called MGCustomLiveBindingMethods, and within it a unit called CurrencyToStrMethod. In it we’re registering two custom methods, CurrencyToStr and StrToCurrency (actually, you can see them in the list of methods above). CurrencyToStr simply takes a Currency and returns a string, formatted with the appropriate currency prefix according to the Locale settings of the underlying operating system, while StrToCurrency does the reverse, stripping all currency formatting out of the string.
unit CurrencyToStrMethod; interface implementation uses System.Bindings.Methods, System.SysUtils, MethodUtils; const sIDCurrencyToStr = 'CurrencyToStr'; sIDStrToCurrency = 'StrToCurrency'; procedure RegisterMethods; begin TMethodUtils.RegisterMethod<Currency, string>(sIDCurrencyToStr, function(AValue: Currency): string begin Result := CurrToStrF(AValue, ffCurrency, 2); end); TMethodUtils.RegisterMethod<string, Currency>(sIDStrToCurrency, function(AValue: string): Currency var C: char; LDigits: string; begin for C in AValue do case C of '0'..'9', '.': LDigits := LDigits + C; end; Result := StrToCurr(LDigits) end); end; procedure UnregisterMethods; begin TBindingMethodsFactory.UnRegisterMethod(sIDCurrencyToStr); TBindingMethodsFactory.UnRegisterMethod(sIDStrToCurrency); end; initialization RegisterMethods; finalization UnregisterMethods; end.
In the Initialization section we’re calling RegisterMethods, which in turn is calling TMethodUtils.RegisterMethod, once for each custom method we want to register. This is a generic method where we can specify the types of parameters expected and returned by our custom method. In the first case, the generic types we’re specifying are Currency and string for the parameter and return types respectively. Given this is for our CurrencyToStr method, that makes sense. The second method we’re registering has these types reversed, which again should make sense given it is for the StrToCurrency method.
The actual parameters to the RegisterMethod call are a string, which is the method name, and an anonymous method which is the actual implementation of our method.
I need to point out that this is not the way you’ll see the default methods registered, and in fact was not the way I was registering my custom methods in the first draft of this article. Prior to TMethodUtils, there was quite a lot of scaffolding code you needed to write to register your methods, but thankfully Jim Tierney came to the rescue and provided this unit which dramatically simplifies the process.
Lastly, and importantly, the finalization section reverses the registration of our methods.
Once this is done, compile the package, then right-click on it in the Project Manager and Install it. Now, back in my project, I can select the binding that connects my Amount field to the TEdit and set its CustomFormat property to CurrencyToStr(%s) to format the Amount appropriately.That’s great for formatting the amount, ie. going from the field to the control, but what happens when we need to go the other way, from the control back to the field, ie. when someone edits the value and we have to strip out the formatting? Well, that’s what the CustomParse property is for, sitting right underneath the CustomFormat property we just used. In here we’ll use StrToCurrency(%s) to undo the formatting.
If you now run up the application, you can see that the Edit box displays the formatted amount correctly. We can edit the value, and the unformatted result is what should end up back in our field (as can be seen by the Label we have bound to the same field).
It’s hopefully apparent by now that the CustomFormat and CustomParse expressions we’re entering are specific to that binding. As above, the grid and the label don’t show the formatting as we have not set the properties on those links. This is another good reason I think to build up a library of your commonly used custom expression methods, so you can very quickly apply them to your links without having to debug complex expressions every time.
That’s enough for this post, but next up, a related topic on writing custom generators for TPrototypeBindSource, and then I want to tackle a bigger topic related to LiveBindings: Model-View-ViewModel (MVVM).