Advanced User Interface Controls and Components
Created: 01 September 2013
If we have large data set, loading of entire data and creation of tree structure will take some time. To avoid this and increase overall performance of our application, we can load data on demand and update the tree structure whenever a new data is needed. Usually for TreeView control the best way to do this is by updating the tree structure whenever a node expands.
IntegralUI TreeView has a feature which enables you to load data on demand from/to XML files, memory streams or databases. There is a built-in serialization consisting of several public methods which you can use and create your own solution.
In this article we are going to explain how to use LoadOnDemand method to add child nodes to currently expanding node. For demonstration purposes we have added also an animated gif which is shown whenever the node expands for the first time. The animation lasts for 750 miliseconds, but you can change it if a loading time is longer. In our case the loading is instant, and the animation is not required, we are using it only for demonstration.
We are using a pre-built XML file called tree.xml, which has a tree structure where each node has Key property set to some unique value. We are using this value to easily locate a specific node and load a partial data from the XML file. There is no restriction on which property you can use in your solution, for example you can also use Tag or TagString properties.
At first because we are going to load data whenever a node is expanded, we need to handle the BeforeExpand event.
private void treeView1_BeforeExpand(object sender, LidorSystems.IntegralUI.ObjectCancelEventArgs e)
{
if (!supressCallback && e.Object is LidorSystems.IntegralUI.Lists.TreeNode)
{
LidorSystems.IntegralUI.Lists.TreeNode node = (LidorSystems.IntegralUI.Lists.TreeNode)e.Object;
// Load the content only once, when node is expanded for the first time
if (String.IsNullOrEmpty(node.TagString))
{
node.AllowImageAnimation = true;
// Start and enable the loading timer which will run the animation
if (!loadTimer.Enabled)
{
loadTimer.Start();
loadTimer.Enabled = true;
}
}
}
}
Private Sub treeView1_BeforeExpand(ByVal sender As Object, ByVal e As LidorSystems.IntegralUI.ObjectCancelEventArgs) Handles treeView1.BeforeExpand
If Not supressCallback AndAlso TypeOf e.[Object] Is LidorSystems.IntegralUI.Lists.TreeNode Then
Dim node As LidorSystems.IntegralUI.Lists.TreeNode = DirectCast(e.[Object], LidorSystems.IntegralUI.Lists.TreeNode)
' Load the content only once, when node is expanded for the first time
If [String].IsNullOrEmpty(node.TagString) Then
node.AllowImageAnimation = True
' Start and enable the loading timer which will run the animation
If Not loadTimer.Enabled Then
loadTimer.Start()
loadTimer.Enabled = True
End If
End If
End If
End Sub
Because we will show animation prior data is loaded, we will add code which will start the loading timer and animation. After animation completes, we can add the code which will load data from a XML file.
// Suspend the control layout from updating to increase performance
this.treeView1.SuspendUpdate();
// Load child nodes and add them to the selected node
TreeViewSerializer serializer = new TreeViewSerializer();
if (System.IO.File.Exists(@fileName))
serializer.LoadOnDemand(this.treeView1, fileName, this.treeView1.SelectedNode, "Key", this.treeView1.SelectedNode.Key);
// Mark that this node has completed load process
this.treeView1.SelectedNode.TagString = "LOAD_COMPLETED";
// Resume the layout calculations and update the control
this.treeView1.ResumeUpdate();
' Suspend the control layout from updating to increase performance
Me.treeView1.SuspendUpdate()
' Load child nodes and add them to the selected node
Dim serializer As New TreeViewSerializer()
If System.IO.File.Exists(fileName) Then
serializer.LoadOnDemand(Me.treeView1, fileName, Me.treeView1.SelectedNode, "Key", Me.treeView1.SelectedNode.Key)
End If
' Mark that this node has completed load process
Me.treeView1.SelectedNode.TagString = "LOAD_COMPLETED"
' Resume the layout calculations and update the control
Me.treeView1.ResumeUpdate()
In code above we are creating an object that will read the content of XML file and apply it to the currently selected node. The method searches through XML file content for a node that matches the value of Key property with the one of currently selected node. If a node is found, we will extract all child nodes from the XML file and add them to the selected node.
We also need to make sure that the node is expanded, and child nodes are shown. But also we need to avoid calling the BeforeExpand event for the second time for this node. This is done by suppressing the event from firing by using a variable supressCallback.
// After content is loaded, expand the node and make sure it doesn't trigger the BeforeExpand event
supressCallback = true;
this.treeView1.SelectedNode.Expand();
supressCallback = false;
' After content is loaded, expand the node and make sure it doesn't trigger the BeforeExpand event
supressCallback = True
Me.treeView1.SelectedNode.Expand()
supressCallback = False
When all this is completed we can end the loading timer and animation and remove the animated gif from the node.
// If animation is running, stop it and remove the animated gif from the node
isAnimationActive = false;
this.treeView1.StopAnimation(this.treeView1.SelectedNode);
this.treeView1.SelectedNode.SelectedImage = null;
// Stop the loading timer
if (loadTimer.Enabled)
{
loadTimerValue = 0;
loadTimer.Enabled = false;
loadTimer.Stop();
}
' If animation is running, stop it and remove the animated gif from the node
isAnimationActive = False
Me.treeView1.StopAnimation(Me.treeView1.SelectedNode)
Me.treeView1.SelectedNode.SelectedImage = Nothing
' Stop the loading timer
If loadTimer.Enabled Then
loadTimerValue = 0
loadTimer.Enabled = False
loadTimer.[Stop]()
End If
To better represent the change when node is expanded or collapsed, we are also using a different image. To do this we are handling the AfterExpand and AfterCollapse events, where we change the node image to show open folder image whenever a node is expanded, and close folder image whenever a node is collapsed.
private void treeView1_AfterExpand(object sender, LidorSystems.IntegralUI.ObjectEventArgs e)
{
// Change the icon of the node to show opened folder after node gets expanded
if (e.Object is LidorSystems.IntegralUI.Lists.TreeNode)
{
LidorSystems.IntegralUI.Lists.TreeNode node = (LidorSystems.IntegralUI.Lists.TreeNode)e.Object;
node.ImageIndex = 1;
node.SelectedImageIndex = 1;
}
}
private void treeView1_AfterCollapse(object sender, LidorSystems.IntegralUI.ObjectEventArgs e)
{
// Change the icon of the node to show closed folder after node gets expanded
if (e.Object is LidorSystems.IntegralUI.Lists.TreeNode)
{
LidorSystems.IntegralUI.Lists.TreeNode node = (LidorSystems.IntegralUI.Lists.TreeNode)e.Object;
node.ImageIndex = 0;
node.SelectedImageIndex = 0;
}
}
Private Sub treeView1_AfterExpand(ByVal sender As Object, ByVal e As LidorSystems.IntegralUI.ObjectEventArgs) Handles treeView1.AfterExpand
' Change the icon of the node to show opened folder after node gets expanded
If TypeOf e.[Object] Is LidorSystems.IntegralUI.Lists.TreeNode Then
Dim node As LidorSystems.IntegralUI.Lists.TreeNode = DirectCast(e.[Object], LidorSystems.IntegralUI.Lists.TreeNode)
node.ImageIndex = 1
node.SelectedImageIndex = 1
End If
End Sub
Private Sub treeView1_AfterCollapse(ByVal sender As Object, ByVal e As LidorSystems.IntegralUI.ObjectEventArgs) Handles treeView1.AfterCollapse
' Change the icon of the node to show closed folder after node gets expanded
If TypeOf e.[Object] Is LidorSystems.IntegralUI.Lists.TreeNode Then
Dim node As LidorSystems.IntegralUI.Lists.TreeNode = DirectCast(e.[Object], LidorSystems.IntegralUI.Lists.TreeNode)
node.ImageIndex = 0
node.SelectedImageIndex = 0
End If
End Sub
Related: Show Different Image on Expand in TreeView
A sample project in C# and VB showing how to load data on demand from XML files is available for download from here: TreeView Load On Demand