Skip to content

Data binding

A binding connects a value in your application to elements of the drawing. You say which elements with a selector, and what to write, an attribute or the text content, and then assign the value. The component writes it and repaints.

Bindings replace the find-and-set code of the previous pages for the common case: a status drives a colour, a measurement drives a label or a needle.

A first binding

Every SVG component has a SVGBindings collection, so bindings can be set up in the Object Inspector as well as in code:

uses
  BVE.SVG2Bindings,
  BVE.SVG2Types;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVGBindings.Bind('needle', 'transform');
end;

procedure TForm1.SetPressure(const aBar: Double);
begin
  SVG2Image1.SVGBindings[0].Value :=
    Format('rotate(%.1f 50 50)', [aBar * 2.7], USFormatSettings);
end;

Assigning Value writes it to every element the selector finds and repaints the component. There is no separate apply or repaint step.

Choosing the elements

SelectorKind decides how Selector is read:

SelectorKind Selector is Finds
skID an element id, 'needle' or '#needle' that element
skTag an element name, 'rect' every rect in the drawing
skClass a class name, 'valve' or '.valve' every element with that class
skAttribute an attribute name, 'data-tag' every element carrying it

An id may be written with a leading # and a class with a leading ., as in CSS. The prefix is optional.

Only skID is limited to one element. The other three drive a whole set from one binding:

// Every element the drawing marks class="alarm" turns red at once
SVG2Image1.SVGBindings.Bind('alarm', 'fill', skClass).Value := 'red';

For skAttribute, SelectorValue is the value to match. Set MatchSelectorValue to False to match every element that has the attribute, whatever its value:

var
  b: TSVGBinding;
begin
  b := SVG2Image1.SVGBindings.Bind('data-tag', 'fill', skAttribute);
  b.SelectorValue := 'PT-102';
  b.Value := 'orange';

Choosing what to write

TargetKind is either tkAttribute, which writes the attribute named by TargetName, or tkText, which replaces the text of the element. Text is what a dashboard needs to show a reading:

SVG2Image1.SVGBindings.Bind('readout', '', skID, tkText).Value := 42.5;

Value is a Variant. For text it is converted with VarToStr; for an attribute a number is written in SVG syntax, with a dot, so it needs no formatting. Values you build as strings, such as a transform, still need USFormatSettings (see Numbers in SVG always use a dot).

Two bindings on one element

Two bindings may write the same attribute of the same element. A group of lamps can be lit all at once by class and individually by id. The element keeps whatever was written last.

Assigning a binding a value it already holds does nothing, which keeps a binding cheap to drive from a timer. But "already holds" is about the binding, not the element: if another binding has written the element since, a plain assignment will not write it again. Apply always writes:

// Light every lamp at once, by class
FAllLamps.Value := ColorAlarm;

// ...and later put each lamp back to its own state. The value may be
// unchanged since before the lamp test, so assigning it would do nothing;
// Apply writes it anyway, over what the class binding left behind.
FLampReactor.Value := LampColor(ReactorHigh);
FLampReactor.Apply;

Loading another drawing

Bindings are not tied to the elements of one document. After the component loads a drawing, it finds the elements again and writes the current values to them. Bindings set up once in the designer keep working when the drawing is replaced, as long as the new drawing has the elements the selectors name.

A selector that matches nothing is not an error; the binding simply writes nowhere. ElementCount tells how many elements a binding currently drives, which is the quickest way to check a drawing against the application:

if SVG2Image1.SVGBindings[0].ElementCount = 0 then
  ShowMessage('The drawing has no element for this binding');

Cost

A binding finds its elements once and keeps them, so driving it from a timer does not search the drawing on every tick. Only skID is answered from the id index; the other kinds walk the drawing the first time they are used, and again after a drawing is loaded or the selector changes.

A worked example

Examples/DataBinding in the examples repository drives a schematic of a small chemical process entirely through bindings: feed tank, pump, heater, reactor, valve, product tank and three gauges. It uses every selector kind and both target kinds. The drawing that comes with it shows how to prepare an SVG for binding: ids on the parts that move, a class on the pipes and the alarm lamps, and a data-tag on each instrument.

Frameworks

Bindings work on VCL, FMX and FPC/Lazarus: TSVG2Image, TSVG2Control and TSVG2WinControl on VCL, TSVG2Image and TSVG2Control on FMX. The engine itself, BVE.SVG2Bindings, does not depend on a framework and works on an ISVGRoot, so it can also be used without a component. The FPC components, TSVG2Image, TSVG2Control and TSVG2WinControl, have the SVGBindings collection as well.

The members of the collection and of a binding are listed in the API reference.