Skip to content

Data binding. Bidirectional mode#

Flexible TreeView supports one-way and bidirectional (two-way) binding modes. Bidirectional binding mode allows synchronization of changes between the treeview and data source without writing a single line of code.

Warning

If synchronization of the changes can't be made automatically, Flexible TreeView neither performs any actions nor gives alerts. Therefore, you must strictly follow the requirements specified in this topic.

Prepare data source#

For automatic bidirectional binding, your data source should be of the DataTable type or a type that implements IBindingList and IList. For other types, a semi-automatic change of data source is possible.

While in bound mode, treeview still allows manipulation of its structure. You can add or delete bound nodes, and even add non-bound nodes to the treeview in bound mode.

There are two ways to change the treeview data structure:

  1. Adding or deleting objects in the bound data source (see Edit data source section below), which is followed by changes in the treeview structure.
  2. Adding or deleting nodes in the treeview (see Edit treeview section below), which is followed by changes in the data source.

Note

When data objects (data table row, array item, etc.) are deleted from the bound data source (which is followed by changes in a treeview structure), automatic synchronization with the bound treeview is supported only for DataTable or IBindingList data source types. To solve this problem, you can delete nodes in the treeview instead, while the objects will be automatically deleted from any data source that inherits from IBindingList, IList or of DataTable type.

To synchronize changes between the treeview and data source automatically, apply the following rules:

  • With the DataBinding.BidirectionalMode treeview property enabled and DataBinding.BoundMode disabled, automatic synchronization (all operations, except object fields editing) will proceed anyway!
  • When a new node is added to the treeview, a new data source object is added only if the data source is an instance or inheritor of DataTable or IBindingList types. For other data source types, the new object must be created manually by the user. Read Tune synchronization results for details.
  • When an object is deleted from the data source, automatic data synchronization is supported for the DataTable or IBindingList data source types only. Read Edit data source for details.
  • All subordinate objects will be deleted automatically when a node or object is deleted.
  • For hierarchical data sources, the property type indicated by the DataBinding.ParentFieldName property must be a reference type to acquire a null value.
  • If Flexible TreeView cannot create a new data source object automatically, the user must catch the NodeInserted event and create the object manually. Read Tune synchronization results for details.

Supported edit operations#

Flexible TreeView provides automatic synchronization of changes for the following operations in the treeview or data source:

  • Adding an object to the data source or node to the treeview.
  • Deleting an object from the data source or node from the treeview. Deleting rows from the data source is supported for DataTable or IBindingList data source types only.
  • When a node is deleted from the treeview, any type of bound data source will be synchronized with these changes.
  • Dragging a node within one treeview.

Edit data source#

One way of editing treeview data is by adding and deleting objects in the bound data source. In bidirectional mode, types indicated in the Prepare data source section above can be used as a data source. The only exception is: object deletion from the data source may be traced only when the source is of the DataTable or IBindingList types.

For example, say we have the following data source object class:

class DataObject
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Text { get; set; }
}
Class DataObject
    Public Property Id() As Integer
    Public Property ParentId() As System.Nullable(Of Integer)
    Public Property Text() As String
End Class

This is how we can build the data source, bind it to the treeview, and enable bidirectional binding mode:

// prepare data source once.
BindingList<DataObject> list = LoadDataSource();
tree.DataBinding.KeyFieldName = "Id";
tree.DataBinding.ParentFieldName = "ParentId";
tree.DataBinding.DataSource = list;
// enable bidirectional binding mode.
tree.DataBinding.BidirectionalMode = true;

BindingList<DataObject> LoadDataSource()
{
  BindingList<DataObject> list = new BindingList<DataObject>();
  list.Add(new DataObject
                 {
                   Id = 1,
                   ParentId = null,
                   Text = "1"
                 });
  list.Add(new DataObject
                 {
                   Id = 2,
                   ParentId = 1,
                   Text = "2"
                 });
  return list;
}
' prepare data source once.
Dim list As BindingList(Of DataObject) = LoadDataSource()
tree.DataBinding.KeyFieldName = "Id"
tree.DataBinding.ParentFieldName = "ParentId"
tree.DataBinding.DataSource = list
' enable bidirectional binding mode.
tree.DataBinding.BidirectionalMode = True

Function LoadDataSource() As BindingList(Of DataObject)
    Dim list As New BindingList(Of DataObject)()
    list.Add(New DataObject With {
        .Id = 1,
        .ParentId = Nothing,
        .Text = "1"
    })
    list.Add(New DataObject With {
        .Id = 2,
        .ParentId = 1,
        .Text = "2"
    })
    Return list
End Function

As a result, the following treeview is generated:

Treeview Result

Then you may add a new object into this data source, which will automatically be followed with the node addition to the tree:

// create new object.
DataObject newObject = new DataObject();
newObject.Id = 3;
newObject.ParentId = 2;
newObject.Text="third level object";

// add new object to the data source.
BindingList<DataObject> list = (BindingList<DataObject>)tree.DataBinding.DataSource;
list.Add(newObject);
' create new object.
Dim newObject As New DataObject()
newObject.Id = 3
newObject.ParentId = 2
newObject.Text = "third level object"

' add new object to the data source.
Dim list As BindingList(Of DataObject) = DirectCast(tree.DataBinding.DataSource, BindingList(Of DataObject))
list.Add(newObject)

And there is no need to trace the treeview changes and synchronize them with the data source.

Note

When a new object is added to the data source, a generated node will be added to the end of child nodes of the parent node list.

If you need to delete an object from the data source and a corresponding node from the treeview, you may just delete this object from the bound data source:

BindingList<DataObject> list = (BindingList<DataObject>)tree.DataBinding.DataSource;
list.RemoveAt(objectIndex);
Dim list As BindingList(Of DataObject) = DirectCast(tree.DataBinding.DataSource, BindingList(Of DataObject))
list.RemoveAt(objectIndex)

Note

On object deletion, all child objects will be deleted as well!

If columns sorting is enabled within the tree, after data source object deletion or addition, you need to resort the treeview manually, as its structure has been changed:

// remove first object in the data source.
DataTable table = (DataTable)tree.DataBinding.DataSource;
table.Rows.RemoveAt(0);

// resort treeview after linked node has been removed.
tree.ReSort();
' remove first object in the data source.
Dim table As DataTable = DirectCast(tree.DataBinding.DataSource, DataTable)
table.Rows.RemoveAt(0)

' resort treeview after linked node has been removed.
tree.ReSort()

Edit treeview#

Addition and deletion of the nodes in the treeview is another way of data editing. As a result, the corresponding objects will be added or deleted in the data source when bidirectional binding mode is enabled.

For example, if we bind the data source like below (LoadDataSource method is defined above):

BindingList<DataObject> list = LoadDataSource();
tree.DataBinding.KeyFieldName = "Id";
tree.DataBinding.ParentFieldName = "ParentId";
tree.DataBinding.DataSource = list;
// enable bidirectional binding mode.
tree.DataBinding.BidirectionalMode = true;
Dim list As BindingList(Of DataObject) = LoadDataSource()
tree.DataBinding.KeyFieldName = "Id"
tree.DataBinding.ParentFieldName = "ParentId"
tree.DataBinding.DataSource = list
' enable bidirectional binding mode.
tree.DataBinding.BidirectionalMode = True

the following treeview is generated:

Treeview Generated

You can delete the single root node (with 1 title) by using the Detach method:

tree.Nodes[0].Detach();
tree.Nodes(0).Detach()

The code above will automatically delete all the objects bound both to the node 1 and to all its child nodes because the child nodes are auto-deleted once the parent node is deleted.

In order to automatically add a new object into the data source, you need to add a node into the treeview, which will automatically create an object in the bound data source:

int? newObjectId = 3;
BindableNode node = new BindableNode(newObjectId);
node.AttachTo(tree.Nodes[0]);
Dim newObjectId As System.Nullable(Of Integer) = 3
Dim node As New BindableNode(newObjectId)
node.AttachTo(tree.Nodes(0))

Note

When creating a node, you need to pass a unique identifier of a new object to the BindableNode class constructor, if you're using a hierarchical data source. The data type of that identifier must be identical to property type indicated by DataBinding.ParentFieldName.

There are some cases, when you need to create a new object in the data source manually, as the automatic addition logic is not appropriate. In this case when adding a node to the treeview, the BoundObject node property must not be set to null:

int? newObjectId = 3;
BindableNode node = new BindableNode(newObjectId);
node.BoundObject = "temp fake object";
node.AttachTo(tree.Nodes[0]);
Dim newObjectId As System.Nullable(Of Integer) = 3
Dim node As New BindableNode(newObjectId)
node.BoundObject = "temp fake object"
node.AttachTo(tree.Nodes(0))

In this case, Flexible TreeView does not generate a data source object for such a node, a user must handle the NodeInserted treeview event, create an object manually and add it to the bound data source as shown below.

void tree_NodeInserted(FlexibleTreeView treeview, Node args)
{
  BindableNode node = args as BindableNode;
  if (node != null && node.BoundObject != null)
  {
    // create new object.
    DataObject newObject = new DataObject();

    // new object's identifier should be passed to the BindableNode class's constructor so we can use it as the new object's Id value.
    newObject.Id = (int)node.Id;

    // get the parent node's Id to fill the ParentId property value.
    BindableNode parentNode = node.Parent as BindableNode;
    int? parentId = parentNode != null ? (int?)parentNode.Id : null;
    newObject.ParentId = parentId;

    // fill other properties by default.
    newObject.Text = "New object";

    // add new object into the data source.
    BindingList<DataObject> list = (BindingList<DataObject>)tree.DataBinding.DataSource;
    list.Add(newObject);

    // IMPORTANT! Newly create object should be linked with apropriate node.
    node.BoundObject = newObject;
  }
}
Private Sub tree_NodeInserted(treeview As FlexibleTreeView, args As Node)
    Dim node As BindableNode = TryCast(args, BindableNode)
    If node IsNot Nothing AndAlso node.BoundObject IsNot Nothing Then
        ' create new object.
        Dim newObject As New DataObject()

        ' new object's identifier should be passed to the BindableNode class's constructor so we can use it as the new object's Id value.
        newObject.Id = CInt(node.Id)

        ' get the parent node's Id to fill the ParentId property value.
        Dim parentNode As BindableNode = TryCast(node.Parent, BindableNode)
        Dim parentId As System.Nullable(Of Integer) = If(parentNode IsNot Nothing, DirectCast(parentNode.Id, System.Nullable(Of Integer)), Nothing)
        newObject.ParentId = parentId

        ' fill other properties by default.
        newObject.Text = "New object"

        ' add new object into the data source.
        Dim list As BindingList(Of DataObject) = DirectCast(tree.DataBinding.DataSource, BindingList(Of DataObject))
        list.Add(newObject)

        ' IMPORTANT! Newly create object should be linked with apropriate node.
        node.BoundObject = newObject
    End If
End Sub

Warning

Pay attention to the last line in the example above. You are obliged to bind a newly created object to the added node before exit from the event handler.

Mixed binding mode#

Mixed binding mode, when both bound and unbound nodes coexist in the same treeview, is also supported in the bidirectional binding mode, what gives you unlimited possibilities for data manipulations.

Note

The data source will be automatically synchronized with treeview changes only if an instance of the BindableNode class is added to the treeview.

Drag & Drop#

Besides the manual node addition or deletion, Flexible TreeView supports the automatic bound object re-linking to another parent object using drag & drop within the current treeview. You just need to allow drag & drop using the DragDropOptions treeview property and enable bidirectional binding mode by enabling the DataBinding.BidirectionalMode property as shown below.

tree.DragDropOptions.AllowDrag = true;
tree.DragDropOptions.AllowDrop = true;
tree.DataBinding.BidirectionalMode = true;
tree.DragDropOptions.AllowDrag = True
tree.DragDropOptions.AllowDrop = True
tree.DataBinding.BidirectionalMode = True

In this case, the bound data object's property, indicated in DataBinding.ParentFieldName, for the dragged object will automatically be changed to the new parent node's identifier value.

There are some restrictions for this case:

  • The node dragging works only within the current treeview. The node dragging between treeviews does not change the bound data source.
  • If you dragged the node of BindableNode type into a non-BindableNode node, the data source will not be updated then, as there's no possibility to acquire the parent node identifiers.

Tune synchronization results#

It is not always possible to automatically synchronize the treeview and data source, or sometimes you need to monitor this. Therefore Flexible TreeView allows to execute additional user code right after automatic procedures went through.

As the main problem may occur while adding the treeview nodes or data source objects, the main means of control is the NodeInserted treeview event in this case. It is raised right after the node is added to the treeview. A node may be added to the treeview with linked data source when a new object is added to the data source, or when this very node is added manually by the user to create a data source object automatically.

Thus, if there is a possibility that the new object may not be created automatically, you need to handle the NodeInserted event and create a data source object manually. You may find out if Flexible TreeView created a new object with the help of the BoundObject property of the node, passed into the event handler.

For example, the following code creates a new object in the data source and binds it to the added node in the only case if Flexible TreeView did not create the object automatically:

void tree_NodeInserted(FlexibleTreeView treeview, Node args)
{
  BindableNode node = args as BindableNode;
  if (node != null && node.BoundObject == null)
  {
    // create new object with Id=5 and link it with a parent object with Id=1.
    DataObject newObject = new DataObject();
    newObject.Id = 5;
    newObject.ParentId = 1;
    newObject.Text = "3";

    // add created object to the data source.
    BindingList<DataObject> list = (BindingList<DataObject>)tree.DataBinding.DataSource;
    list.Add(newObject);

    // IMPORTANT! Newly create object should be linked with apropriate node.
    node.BoundObject = newObject;
  }
}
Private Sub tree_NodeInserted(treeview As FlexibleTreeView, args As Node)
    Dim node As BindableNode = TryCast(args, BindableNode)
    If node IsNot Nothing AndAlso node.BoundObject Is Nothing Then
        ' create new object with Id=5 and link it with a parent object with Id=1.
        Dim newObject As New DataObject()
        newObject.Id = 5
        newObject.ParentId = 1
        newObject.Text = "3"

        ' add created object to the data source.
        Dim list As BindingList(Of DataObject) = DirectCast(tree.DataBinding.DataSource, BindingList(Of DataObject))
        list.Add(newObject)

        ' IMPORTANT! Newly create object should be linked with apropriate node.
        node.BoundObject = newObject
    End If
End Sub

Note

We execute the code only if the object hasn't been created automatically!

Also, NodeInserted may be useful when the object has been created by Flexible TreeView automatically, however there is a necessity to tune it or initialize some object fields after creation:

void tree_NodeInserted(FlexibleTreeView treeview, Node args)
{
  BindableNode node = args as BindableNode;
  if (node != null && node.BoundObject != null)
  {
    DataObject createdObject = (DataObject)node.BoundObject;
    createdObject.Text = "New object";
  }
}
Private Sub tree_NodeInserted(treeview As FlexibleTreeView, args As Node)
    Dim node As BindableNode = TryCast(args, BindableNode)
    If node IsNot Nothing AndAlso node.BoundObject IsNot Nothing Then
        Dim createdObject As DataObject = DirectCast(node.BoundObject, DataObject)
        createdObject.Text = "New object"
    End If
End Sub