Class Binding

GoDiagram®
v10.0.8
by Northwoods Software®

A Binding describes how to automatically set a property on a GraphObject to a value of a property of data in the model. The target property name and the data source property name are strings. All name matching is case-sensitive.

Inheritance
Binding
Namespace: Northwoods.Go.Models
Assembly: Northwoods.GoDiagram.WinForms.dll
Syntax
public class Binding
Remarks

Register bindings by calling Bind(params Binding[]). Existing bindings become read-only, and no new bindings may be added, when a template (a Part) is copied. Bindings will be shared by all copies of the template's GraphObjects.

For example, your node data might be like:

{ Key = 23, Say = "hello!" }

Your simple node template might be like:

var template = new Node(PanelLayoutAuto.Instance);
// . . . define the rest of the Node's visual tree . . .
var txt = new TextBlock();
txt.Bind("Text", "Say");
template.Add(txt);
myDiagram.NodeTemplate = template;

The data binding causes the Text property of the TextBlock to be set to the value of the data's "Say" property. If the value of the "Say" property of a particular data object is null, the binding is not evaluated: the target property is not set. If there is an error with the binding, you may see a message in the console log. For this reason you may want to explicitly set the initial value for a property when defining the GraphObject, since that value will remain as the default value if the Binding is not evaluated.

Bindings are not necessarily evaluated in any particular order. Binding sources should not be (or depend in a conversion function on) the category of the data if you might be modifying the category (e.g. by calling SetCategoryForNodeData(TNodeData, string)), because then some bindings might be evaluated before or after the category has been changed.

Conversions

Sometimes the data value needs to be modified or converted in order to be used as the new value of a GraphObject property. The most common conversion functions are provided for you -- they convert a string to a geometric class: Parse(string), Parse(string), Parse(string), Parse(string), Parse(string), and Parse(string, bool). But you can easily define your own conversion function.

As an example of a conversion function, let's use a function that adds some text prefixing the data property value:

.Bind("Text", "Say", (v, _) => { return "I say: " + v; })

Although simple conversions cover almost all binding cases, there are some infrequent uses that are covered by "Advanced Conversions", discussed below. Conversion functions must not have any side-effects. Conversion functions may be called frequently, so they should be fast and avoid allocating memory. The order in which conversion functions are called is not specified and may vary.

OneWay and TwoWay Bindings

By default bindings are OneWay. OneWay bindings are evaluated when the Data property is set or when you call UpdateTargetBindings(string) or Set(object, string, object). OneWay bindings only transfer values from the source to the target.

TwoWay bindings are evaluated in the source-to-target direction just as OneWay bindings are evaluated. However when the GraphObject target property is set, the TwoWay bindings are evaluated in the target-to-source direction. There is no point in having a TwoWay binding on a GraphObject property that cannot be set. For efficiency, avoid TwoWay bindings on GraphObject properties that do not change value in your app.

You should not have a TwoWay binding with a source that is a data object's key property, i.e. on the data property whose name is the same as the value of NodeKeyProperty or LinkKeyProperty. Unintentionally changing the key value to be the same as another data's key value may cause indeterminate behavior. Furthermore, changing a data key without changing any references to that Part using the key value will result in "dangling" references and inconsistent relationships. You can make that change safely by calling SetKeyForNodeData(TNodeData, TNodeKey) or SetKeyForLinkData(TLinkData, TLinkKey), but not via a data binding.

The target-to-source update can also go through a conversion function. The most common back-conversion functions are provided for you. They convert a geometric class to a string: Stringify(Point), Stringify(Size), Stringify(Rect), Stringify(Margin), Stringify(Spot), and Stringify(Geometry).

It is common to want to update some data properties based on changes to the diagram. For example, as the user changes the Location by dragging a Node, you can automatically keep the node's model data in sync using a TwoWay binding.

new Binding("Location", "Loc", Point.Parse, Point.Stringify)

The call to MakeTwoWay(BackConversion) changes the Mode to be TwoWay and specifies the BackConverter function to be the Stringify(Point) static function.

Because the Binding is on the whole node (template), the target object is the whole Node and the target property is "Location". The value of Data.Loc will be a string representation of the Node.Location value.

Binding Sources

The target of a Binding is always a property of a GraphObject or a RowDefinition/ColumnDefinition. The source of a Binding is normally a property of a data object in the model. But it is also possible to have the source of a Binding be the shared object that is the value of SharedData. You can specify such a binding by calling OfModel(), meaning "a binding of a source that is a property of the Model.SharedData".

As an example, you might want all Nodes to use the same color. It would be possible but not natural to bind to a property on the node data object, because that property would have to be duplicated on all of the node data objects in the model, and updating the property would mean calling Set(object, string, object) on each node data object with the same new value. Furthermore if there happened to be no nodes at all in the model, there would be no place to save the data. Hence using the shared SharedData object would be the sensible place for that shared information.

.Bind(new Binding("Stroke", "StrokeColor").OfModel())

and to set or modify that color one would just call, within a transaction:

model.Set(model.SharedData, "StrokeColor", "red");

That would cause all nodes with that model data binding to be re-evaluated. It is not commonplace to have a TwoWay Binding on "OfModel" Bindings, but that should work. Converters also work with "OfModel" Bindings.

And it is also possible to have the source of a Binding be another GraphObject that is in the same Part. You can enable such a binding by calling OfElement(string), meaning "a binding of a source that is a property of a GraphObject". You just have to make sure that element has a unique Name or is the Part itself. The source property on the GraphObject has to be settable, and the Part must have a value for Data. (If the source property setter does not notify about property value changes, the binding mechanism will not be invoked. Similarly, if there is no Panel.Data, the binding mechanism is not active.)

As a common kind of example of data binding between two properties of GraphObjects, consider this Binding on a Shape which changes the color of the Stroke depending on whether the Node is selected (IsSelected):

.Bind(new Binding("Stroke", "IsSelected", (s, _) => { return (bool)s ? "dodgerblue" : "gray"; }).OfElement())

Note the call to OfElement(string), which tells the Binding that it should use as the source a GraphObject with a particular name. However that name argument is optional -- supplying no name (or supplying an empty string) will cause the binding to operate with the root GraphObject. In this case that would be the Node itself. Now with this binding whenever the value of IsSelected changes, this Shape's stroke changes color. The conversion function is what changes the boolean "IsSelected" value to a brush color specifier.

Advanced Conversions

The binding functionality also has more advanced features for less common situations. The source property name may be an empty string, to convert the object as a whole. Conversion functions may take a second argument that takes the object that is bound. For source-to-target conversions, the second argument will be the GraphObject whose property is bound. For target-to-source (back-)conversions, the second argument will be the source data object and the third argument will be the Model<TNodeData, TNodeKey, TSharedData>.

Here's an example of a two-way data-binding using two custom conversion functions working with two separate data properties. First we define the two conversion functions.

object ToLocation(object data, object node) {
  var nd = (MyNodeData)data;
  return new Point(nd.X, nd.Y);
}

object FromLocation(object loc, object data, IModel model) {
  var pt = (Point)loc;
  model.Set(data, "X", pt.X);
  model.Set(data, "Y", pt.Y);
  return null;
}

Then to data-bind the default template's Location property to two separate data properties, "x" and "y":

.Bind("Location", "", ToLocation, FromLocation)

An empty string argument for the sourceprop parameter indicates that the whole data object should be passed to the ToLocation function, rather than the value of some property of that data. The return value is used as the new value for the Location property. In almost all cases the second argument is not used. Caution: for efficiency reasons you should try to avoid using an empty source property name. Such bindings will be evaluated much more frequently than ones whose source is a particular property name.

The binding works normally for the source-to-target direction. But when the target property is modified it is the source property that is set with the back-converted property value from the target object. Because in this example the source property name is the empty string, and because one cannot replace the whole source data object, any return value from the conversion function is ignored. Instead the conversion function has to modify the data object directly, as this example FromLocation function does.

Note that because the source property name is the empty string, the binding system will not know which properties are modified in the call to FromLocation. Hence to support undo and redo, in order to make the data changes we have to call Set(object, string, object) so that the UndoManager can record the change, including the previous value.

Replacing Items in Arrays

However, although a TwoWay Binding cannot replace the node data object in the NodeDataSource, it is possible to replace an item in an ItemList. So if your node data were:
{ Key = 1, Items = ["one", "two", "three"] }

And if your node template included something like:

new Panel(PanelLayoutVertical.Instance) {
  ItemTemplate =
    new Panel().Add(
      new TextBlock { Editable = true }.Bind(new Binding("Text").MakeTwoWay())
    )
}.Bind("ItemList", "Items")

Then the user would be able to edit any of the TextBlocks, causing the item list to be modified, for example resulting in this node data:

{ Key = 1, Items = ["one", "SOME NEW TEXT HERE", "three"] }

Constructors

Binding(Binding)

Create a copy of this Binding, with the same property values.

Declaration
public Binding(Binding bind)
Parameters
Type Name Description
Binding bind

Binding(string, string, SimpleConversion, BackConversion)

The constructor creates a binding.

Declaration
public Binding(string targetprop, string sourceprop, SimpleConversion conv, BackConversion backconv = null)
Parameters
Type Name Description
string targetprop

A string naming the target property on the target object. This should not be the empty string.

string sourceprop

A string naming the source property on the bound data object. If this is the empty string, the whole Data object is used.

SimpleConversion conv

A side-effect-free function converting the data property value to the value to set the target property. If the function is null or not supplied, no conversion takes place.

BackConversion backconv

An optional conversion function to convert property values back to data values. If the function is null or not supplied, no conversion takes place, and Mode will be OneWay.

Remarks

By default, a one way binding will be created. If a back converter function is supplied, the binding will be made two way.

Binding(string, string, SimpleConversion, SimpleConversion)

The constructor creates a binding.

Declaration
public Binding(string targetprop, string sourceprop, SimpleConversion conv, SimpleConversion backconv)
Parameters
Type Name Description
string targetprop

A string naming the target property on the target object. This should not be the empty string.

string sourceprop

A string naming the source property on the bound data object. If this is the empty string, the whole Data object is used.

SimpleConversion conv

A side-effect-free function converting the data property value to the value to set the target property. If the function is null or not supplied, no conversion takes place.

SimpleConversion backconv

An optional conversion function to convert property values back to data values. If the function is null or not supplied, no conversion takes place, and Mode will be OneWay.

Remarks

By default, a one way binding will be created. If a back converter function is supplied, the binding will be made two way.

Binding(string, string, TargetConversion, BackConversion)

The constructor creates a binding.

Declaration
public Binding(string targetprop, string sourceprop, TargetConversion conv = null, BackConversion backconv = null)
Parameters
Type Name Description
string targetprop

A string naming the target property on the target object. This should not be the empty string.

string sourceprop

A string naming the source property on the bound data object. If this is the empty string, the whole Data object is used.

TargetConversion conv

A side-effect-free function converting the data property value to the value to set the target property. If the function is null or not supplied, no conversion takes place.

BackConversion backconv

An optional conversion function to convert property values back to data values. If the function is null or not supplied, no conversion takes place, and Mode will be OneWay.

Remarks

By default, a one way binding will be created. If a back converter function is supplied, the binding will be made two way.

Binding(string, string, TargetConversion, SimpleConversion)

The constructor creates a binding.

Declaration
public Binding(string targetprop, string sourceprop, TargetConversion conv, SimpleConversion backconv)
Parameters
Type Name Description
string targetprop

A string naming the target property on the target object. This should not be the empty string.

string sourceprop

A string naming the source property on the bound data object. If this is the empty string, the whole Data object is used.

TargetConversion conv

A side-effect-free function converting the data property value to the value to set the target property. If the function is null or not supplied, no conversion takes place.

SimpleConversion backconv

An optional conversion function to convert property values back to data values. If the function is null or not supplied, no conversion takes place, and Mode will be OneWay.

Remarks

By default, a one way binding will be created. If a back converter function is supplied, the binding will be made two way.

Binding(string)

The constructor creates a one-way binding with the same target and source properties.

Declaration
public Binding(string prop)
Parameters
Type Name Description
string prop

A string naming the target/source property on the target object/data. This should not be the empty string.

Properties

BackConverter

Gets or sets a converter function to apply to the GraphObject property value in order to produce the value to set to a data property.

Declaration
public BackConversion BackConverter { get; set; }
Property Value
Type Description
BackConversion
Remarks

This conversion function is only used in a TwoWay binding, when transferring a value from the target to the source. The default value is null -- no conversion takes place. Otherwise the value should be a function that takes three arguments and returns the desired value. However, the return value is ignored when the SourceProperty is the empty string.

Conversion functions must not have any side-effects other than setting the source property.

The function is passed the value from the target (the first argument), the source Data object (the second argument), and the Model<TNodeData, TNodeKey, TSharedData> (the third argument). If the SourceProperty is a property name, that property is set to the function's return value. If the SourceProperty is the empty string, the function should modify the second argument, which will be the source data object.

Converter

Gets or sets a converter function to apply to the data property value in order to produce the value to set to the target property.

Declaration
public TargetConversion Converter { get; set; }
Property Value
Type Description
TargetConversion
Remarks

This conversion function is used in both OneWay and TwoWay bindings, when transferring a value from the source to the target. The default value is null -- no conversion takes place. Otherwise the value should be a function that takes two arguments and returns the desired value. However, the return value is ignored when the TargetProperty is the empty string.

Conversion functions must not have any side-effects other than setting the target property. In particular you should not try to modify the structure of the visual tree in the target GraphObject's Part's visual tree.

The function is passed the value from the source (the first argument) and the target GraphObject (the second argument). If the TargetProperty is a property name, that property is set to the function's return value. If the TargetProperty is the empty string, the function should set a property on the second argument, which will be the target GraphObject.

IsToModel

Gets or sets whether the source data is SharedData rather than a node data or link data object in the model.

Declaration
public bool IsToModel { get; set; }
Property Value
Type Description
bool
Remarks

The default value is false -- the source data object will not be the Model.SharedData object.

See Also

Mode

Gets or sets the directions and frequency in which the binding may be evaluated.

Declaration
public BindingMode Mode { get; set; }
Property Value
Type Description
BindingMode
Remarks

The default value is OneWay. TwoWay is the other choice.

Use OneWay bindings to initialize GraphObject properties based on model data, or to modify GraphObject properties when the model data changes with a call to Set(object, string, object). Use TwoWay bindings to keep model data in sync with changes to GraphObject properties. For efficiency, avoid TwoWay bindings on GraphObject properties that do not change value in your app.

You should not have a TwoWay binding on a data object's key property.

SourceName

Gets or sets the name of the GraphObject that should act as a source object whose property should be gotten by this data binding.

Declaration
public string SourceName { get; set; }
Property Value
Type Description
string
Remarks

The default value is null, which uses the bound Data as the source. If the value is a string, it should be the name of a GraphObject in the visual tree of the Panel that is bound to the data. Use the empty string to refer to the root panel, which is typically the whole Node or Link, but will be a Panel if used in a ItemTemplate. The name must not contain a period.

Binding only works if the source property is settable, not on computed or read-only properties, and if it supports notification. The documentation for the GraphObject (or subclass of GraphObject) property will indicate if the property is settable and if it does not notify.

SourceProperty

Gets or sets the name of the property to get from the bound data object, the value of Data.

Declaration
public string SourceProperty { get; set; }
Property Value
Type Description
string
Remarks

The default value is the empty string, which results in setting the target property to the whole data object, rather than to a property value of the data object. If SourceName is not null, then this property names the settable property on the GraphObject or RowDefinition/ColumnDefinition that acts as the source.

TargetProperty

Gets or sets the name of the property to be set on the target GraphObject.

Declaration
public string TargetProperty { get; set; }
Property Value
Type Description
string
Remarks

The default value is the empty string; you should set this to be the name of a property.

Methods

MakeTwoWay(BackConversion)

Modify this Binding to set its Mode to be TwoWay, and provide an optional conversion function to convert GraphObject property values back to data values, as the value of BackConverter.

Declaration
public Binding MakeTwoWay(BackConversion backconv = null)
Parameters
Type Name Description
BackConversion backconv
Returns
Type Description
Binding

this two-way Binding.

Remarks

Use TwoWay bindings to keep model data in sync with changes to GraphObject properties. For efficiency, avoid TwoWay bindings on GraphObject properties that do not change value in your app. It is typical only to use TwoWay bindings on properties that are modified by tools or commands. Examples include Location by DraggingTool and Text by TextEditingTool (only if Editable is true).

You should not have a TwoWay binding on a data object's key property.

MakeTwoWay(SimpleConversion)

Modify this Binding to set its Mode to be TwoWay, and provide an optional conversion function to convert GraphObject property values back to data values, as the value of BackConverter.

Declaration
public Binding MakeTwoWay(SimpleConversion backconv)
Parameters
Type Name Description
SimpleConversion backconv
Returns
Type Description
Binding

this two-way Binding.

Remarks

Use TwoWay bindings to keep model data in sync with changes to GraphObject properties. For efficiency, avoid TwoWay bindings on GraphObject properties that do not change value in your app. It is typical only to use TwoWay bindings on properties that are modified by tools or commands. Examples include Location by DraggingTool and Text by TextEditingTool (only if Editable is true).

You should not have a TwoWay binding on a data object's key property.

OfElement(string)

Modify this Binding to set its SourceName property so as to identify a GraphObject in the visual tree of the bound Panel as the data source, instead of the Data as the data source.

Declaration
public Binding OfElement(string srcname = "")
Parameters
Type Name Description
string srcname

the Name of an element in the visual tree of the bound Panel; use an empty string to refer to the root panel of that visual tree, whose Data is the bound data.

Returns
Type Description
Binding

this Binding to another GraphObject.

Remarks

This permits data binding on GraphObject properties, such as IsSelected. Remember that you can reliably data bind only on settable properties, not on read-only or computed properties.

See Also

OfModel()

Modify this Binding so that the source is the SharedData object, not a regular data object or another GraphObject in the Part.

Declaration
public Binding OfModel()
Returns
Type Description
Binding

this Binding to the SharedData object.

See Also