LiveBindings in XE3: Formatting your Fields

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 DisplayFormat property of a TFloatField being used to control the formatting of the text in the linked Edit box.

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.

Create a new FireMonkey Desktop application and drop down a TPrototypeBindSource. Next, define two fields like so:

Then, bind those fields to some controls as shown below:


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.

The CustomFormat expression we’re using to format the Lastname column in the Grid.

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.

The methods available to our binding and format expressions, including my custom CurrencyToStr and StrToCurrency methods.

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.

NB: MethodUtils will at some point roll back into the core product, at which point I’ll need to remove it from my package. If you expect to use this unit in multiple packages, you should either rename it or put it in its own, standalone package.

Lastly, and importantly, the finalization section reverses the registration of our methods.

The CustomFormat and CustomParse properties of our binding, using our custom 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).

Our form at runtime. Note we have targeted the formatting only at the specific control we want, in this case, the Amount Edit.

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).

13 Comments

  • LDigits has to be properly initialized (set to empty prior the loop)?

  • LDigits has to be initialized prior the loop?

    • @user12 No, as the local String variable is a managed type, the Delphi compiler will initialize it for us. You’ll sometimes see a “Variable ‘blah’ might not have been initialised” warning with other types, but not in this case.

  • When using a bind type TLinkControlToField can not seek to inform the property values ​​for the object property as an option on the property TBindExpression ControlExpression, not TLinkControlToField would I change it? eg have a TEdit with the property in it has caught Value wanted to?

    • @robhotyco Sorry, not sure I understand the question. Can you exlpain it further?

  • forgot to post the original error Thank you.
     
    EvalError in LinkControlToField20: ‘1.200,00’ is not a valid floating point value.

  • Imagine that the Person object has a property of type Double Salaryconnected to an edit using TLinkControlToField and the value of edit imformado is where the value of the salary would be 1200.00 and that value of conversion “in EvalError LinkControlToField20: .200,00 ‘1 ‘is not a valid floating point value” as Nesser carry case?

  • Hello Malcolm,
    I am so used to set TField.DisplayLabel, TField.DisplayWidth & TField.Visible on a static field list of a TDataSet and have it reflected in the data grid. Is there a more elegant solution than doing this all again in the column list of TGrid (FMX)? If I need to implement my TGrid descendent I get back to the old fashing “data-aware” controls.
    Salut,
      Mathias

  • Hello, thanks for a great set of articles.I tried to build similar custom format (Iso8601ToStr and StrToIso8601).
    1) I built and installed IDE package and I can see them both in list of  LiveBinding Methods
    2) I assign them to a LiveBinding as in example e.g.:  CustomFormat=  Iso8601ToStr(%s)
    3)but when I run the program , I get error:EEvaluatorError with message ‘Couldn’t find Iso8601ToStr’Can you please tell me what I might be missing?
    Thanks
    AntonE

    • Hello AntonE.

      You must include the unit, in which the Iso8601ToStr method is declared, into the uses clause of your form.

  • “The good news is that if you are binding from a TDataset using a TBindSourceDB, all of these will be respected by LiveBindings.”
     
    When I use a TADOdataset with a TBindingSourceDB the formatting of a datetimefield is not working. I want to display only the time, so  I set the DisplayFormat of the Field in the DataSet as ‘hh:nn’.  
    But the grid is still displaying  the whole field e.g. ’01-01-1900 08:01’…..
     
    Am I forgetting something?

  • […] actually covered the key parts of the solution in my post LiveBindings in XE3: Formatting your Fields. In that post I used the CustomFormat and CustomParse expression in my links to add Currency […]

  • Hello Malcolm.

    I’ve created a new unit with the intention of convert the boolean value of a CheckBox to a char. For “True” it must be ‘Y’ and for “False” it must be ‘N’.

    I’m sorry but they don’t works. The results are very strange. I can’t understand what are the problems.


    ...
    const
    sIDscBooleanToStr = 'scBooleanToStr';
    sIDscStrToBoolean = 'scStrToBoolean';

    procedure RegisterMethods;
    begin
    TMethodUtils.RegisterMethod(sIDscBooleanToStr,
    function(AValue: Boolean): string
    begin
    case AValue of
    True : Result := 'Y';
    False: Result := 'N';
    end;
    end);

    TMethodUtils.RegisterMethod(sIDscStrToBoolean,
    function(AValue: string): Boolean
    begin
    Result := Trim(AValue) = 'True';
    end);
    end;
    ...

    I used CustomFormat ==>> scBooleanToStr(%s)
    and CustomParse ==>> scStrToBoolean(%s)

    Delphi XE5 Pro Update 1 with Mobility and FireDAC.

    Thank you, for these interesting articles.

    Juan C.Cilleruelo

Leave a Reply to AntonE Cancel reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>