Learn Microsoft Access Advanced Programming Techniques, Tips and Tricks.

Rearrange TreeView Nodes by Drag and Drop

Introduction.

I hope you found last week’s tutorial on the ImageCombo Control useful in your Microsoft Access projects. With the TreeView-style ImageCombo, we were able to create a compact yet versatile drop-down menu that accommodates multiple options while occupying very little space on a form.

In an earlier session, we explored how to add new Nodes at specific positions in the hierarchy, or delete and reinsert Nodes to relocate them on the TreeView control.

That method required creating a new record in the source table (for new Nodes) or deleting and recreating records (to move existing Nodes), ensuring the changes were stored permanently. Using these Add/Delete functions, we could build or restructure the Node hierarchy as needed.

However, when it comes to rearranging Nodes, there’s a more efficient approach than repeatedly deleting and recreating them: simply drag the Node from its current position and drop it into its new location within the TreeView control. That’s exactly what we’ll be learning in this episode.

The advantage of this method is its simplicity. Instead of complex record manipulations, we only need to update the ParentID field of the affected records. This single change makes the new Node arrangement permanent.

The Topics Covered so far in Earlier Sessions.

  1. Microsoft TreeView Control Tutorial
  2. Creating an Access Menu with a TreeView Control
  3. Assigning Images to TreeView Control
  4. Assigning Images to TreeView Control-2
  5. TreeView Control Check-Mark Add, Delete Nodes
  6. TreeView ImageCombo Drop-down Access Menu

However, while this method is simpler, it does come with a few challenges and limitations that we should be aware of. We’ll explore these issues in detail a little later in this session, along with the techniques to handle them effectively.

Demo Data Table and Form.

For this exercise, we need a Table and a Form. Fortunately, we already have a suitable table named Sample, which we created in an earlier tutorial session. If you have previously downloaded the Demo Database from the second link provided above, you can continue using that database for this session as well. We will make use of the following objects from that database for our drag-and-drop experiments:

  • Table: Sample
  • Form: frmSample

The TreeView Control Image on frmSample with demo data is given below for reference:

You can download the Demo Database (ProjectMenu.zip) from the second link provided above, then extract the file to obtain ProjectMenu.accdb.

New Form for Drag and Drop Trial Runs.

  1. Open the ProjectMenu.accdb database.

  2. First, make a copy of the Sample table and name it Sample_bk. This backup ensures that the original data remains safe for later use. During our drag-and-drop experiments, the ParentID field values in the Sample table will be updated, but we will need the original data intact afterward.

  3. Next, create a new form and name it frmDragDrop.

  4. Once you have finished designing the form, it will resemble the illustration shown below.


  1. Insert the TreeView Control

    • From the ActiveX Controls list, insert a TreeView Control onto the form.

    • Leave enough space above the control to accommodate two command buttons and a heading label.

    • Drag the sizing handle at the bottom-right corner to enlarge the TreeView so that it can display all the nodes without the need for scrolling.

    • Change the Name property of the TreeView control to TreeView0.

  2. Add the Command Buttons

    • Insert a Command Button above the left edge of the TreeView control.

      • Set its Name property to cmdExpand.

      • Set its Caption property to Expand All.

    • Insert another Command Button above the right edge of the TreeView control.

      • Set its Name property to cmdCollapse.

      • Set its Caption property to Collapse All.

  3. Add the Heading Label

    • Insert a Label Control above the command buttons.

    • Make it wide enough to display the heading as shown in the reference image.

    • Increase the Font Size to 14 for better visibility.

  4. Skip the ImageList for Now

    • For the time being, ignore the ImageList Control.

    • The code lines that modify the Node’s ImageList index numbers have been commented out.

    • Later, you can import the ImageList Control along with manually uploaded images from our earlier Tutorial Demo Database (see the 4th Link Page mentioned previously).

    • This will allow you to display images on the nodes.

    • When node positions are changed during drag-and-drop actions, the node images should also be updated to reflect their new positions (whether they are root-level nodes or child nodes).

    Drag-Drop Form Module Code.

  5. Open the VBA Code Module of the form frmDragDrop.
    Copy and paste the following VBA code (this is only the first half of the form’s module code) into the frmDragDrop Form’s Class Module, and then save the form:

    Option Compare Database
    Option Explicit
    
    Dim tv As MSComctlLib.TreeView
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim imgListObj As MSComctlLib.ImageList
    Const KeyPrfx As String = "X"
    
    Private Sub Form_Open(Cancel As Integer)
    Set tv = Me.TreeView0.Object
    
    'Set imgListObj = Me.ImageList1.Object
    'tv.ImageList = imgListObj
    
    LoadTreeView
    
    End Sub
    
    Sub LoadTreeView()
    Dim strKey As String
    Dim strPKey As String
    Dim strText As String
    Dim strsQL As String
    
    strsQL = "SELECT * FROM Sample ORDER BY ID"
    
    Set db = CurrentDb
    Set rst = db.OpenRecordset(strsQL, dbOpenDynaset)
        
    tv.Nodes.Clear
    
    'Add all Items are added as Root Nodes
    Do While Not rst.BOF And Not rst.EOF
        strKey = KeyPrfx & CStr(rst!ID)
        strText = rst!desc
        tv.Nodes.Add , , strKey, strText
        
        'With tv.Nodes.Item(strKey)
        '    .Image = 1
        '    .SelectedImage = 4
        'End With
    
        rst.MoveNext
    Loop
    
    'Prepare to update the Parent-Key of Nodes
    'wherever applicable to move and position the Child Nodes
    strPKey = ""
    rst.MoveFirst
    Do While Not rst.EOF
        strPKey = Nz(rst!parentid, "")
        
        If Len(strPKey) > 0 Then
            strPKey = KeyPrfx & strPKey
            strKey = KeyPrfx & CStr(rst!ID)
            strText = rst!desc
            
            'Move the Child Node under it's Parent-Node
            Set tv.Nodes.Item(strKey).Parent = tv.Nodes.Item(strPKey)
            
    'Update Image and SelectedImage Properties
    'with ImageList Index numbers
            'With tv.Nodes.Item(strKey)
            '    .Image = 2
            '    .SelectedImage = 3
            'End With
    
        End If
        rst.MoveNext
    Loop
    
    rst.Close
    Set rst = Nothing
    Set db = Nothing
    
    End Sub
    
    
    Private Sub TreeView0_NodeClick(ByVal Node As Object)
    Dim SelectionNode As MSComctlLib.Node
        
    'Ensure that the clicked node equals the selected node in the tree
    If Not Node Is Nothing Then
        Set SelectionNode = Node
           If SelectionNode.Expanded = True Then
                SelectionNode.Expanded = False
            Else
                SelectionNode.Expanded = True
            End If
    End If
    End Sub
    
    Private Sub cmdCollapse_Click()
    Dim tmpnod As MSComctlLib.Node
    For Each tmpnod In tv.Nodes
        If tmpnod.Expanded = True Then
            tmpnod.Expanded = False
        End If
    Next
    
    End Sub
    
    Private Sub cmdExpand_Click()
    Dim tmpnod As MSComctlLib.Node
    For Each tmpnod In tv.Nodes
        If tmpnod.Expanded = False Then
            tmpnod.Expanded = True
        End If
    Next
    
    End Sub

    If you have followed the earlier episodes, most of the above code should look familiar. The main difference lies in the LoadTreeView() subroutine, which now works in a slightly different way.

    In this version, populating the TreeView nodes is done in two steps:

    1. First pass:
      All records from the Sample table are loaded as root-level nodes in the TreeView control. Each node uses the ID field value as its Key.

    2. Second pass:
      The same records are read again, this time checking for values in the ParentID field.

      • If the ParentID field is empty, the node remains as a root-level node.

      • If the ParentID field contains a value, the code locates the node whose Key matches this ParentID, and then reassigns the current node as its child node (updating the [Relative] parameter of the Add() method accordingly).

    At first glance, this two-step population may look redundant, but there is a very specific reason behind it. We will revisit this later in the session, and the advantage will be clear without needing much explanation.

    On the form design, I have included an ImageList control. You may:

    • Insert the ImageList ActiveX control and upload a few images manually from your disk, or

    • Copy and paste the ImageList control (with preloaded images) from one of the earlier demo databases.

    Make sure the control is named ImageList1. Otherwise, you will need to update the control name in the code.

    After adding the ImageList, enable the commented-out lines in the Form_Open() event procedure by removing the leading apostrophes:

    'Set imgListObj = Me.ImageList1.Object 'tv.ImageList = imgListObj

    Similarly, in the TreeView0_OLEDragDrop() subroutine (found in the second part of the VBA code), remove the comment symbols from the lines that update the Image Index parameters. With these changes, images will appear alongside the TreeView nodes.

    If you already have your own ImageList control with uploaded images, you may adjust the image index numbers in the code to decide which images should appear on each node.

    The TreeView0_NodeClick() event procedure expands the currently selected node if its child nodes are collapsed; otherwise, it collapses them. Normally, this expand/collapse action can be done by clicking the [+] or [–] symbol next to the node, but here it is handled programmatically for more control.

    The cmdExpand_Click() and cmdCollapse_Click() subroutines expand all nodes and collapse all nodes, respectively.

    When you run the code, the form will display as shown in the Form View image below:

    You may save the frmDragDrop Form and open it in Normal View.  If everything went well, then you will see the above screen.  Try out the Expand All and Collapse All Command Buttons and check whether they are working too.  If not, then re-check whether the following settings are correct or not:

    i)  The TreeView Control’s Name is: TreeView0

    ii)  Display the Property Sheet of Exampand All Command Button and select [Event Procedure] in the On Click Event Property.

    iii) Ensure that the same setting is intact for the Collapse All Command Button also. 

    iv) Click on a Node, having Child Nodes, to see whether they collapse or expand on repeated clicks.

     v) If the ImageList Control is placed on the Form, then its name must be ImageList1.

    Let us proceed with the second part of the VBA Code that implements the drag-and-drop events.

    Second Half of the VBA Code.

    Copy the following Second Part of the VBA Code, on the frmDragDrop Form Module, that implements the Drag-Drop action, and paste it below the existing Code:

    Private Sub TreeView0_OLEStartDrag(Data As Object, AllowedEffects As Long)
        Set Me.TreeView0.SelectedItem = Nothing
    End Sub
    
    
    Private Sub TreeView0_OLEDragOver(Data As Object, _
                                    Effect As Long, _
                                    Button As Integer, _
                                    Shift As Integer, _
                                    x As Single, _
                                    y As Single, _
                                    State As Integer)
        
        Dim SelectedNode As MSComctlLib.Node
        Dim nodOver As MSComctlLib.Node
        
        If tv.SelectedItem Is Nothing Then
            'Select a node if one is not selected
            Set SelectedNode = tv.HitTest(x, y)
            If Not SelectedNode Is Nothing Then
                SelectedNode.Selected = True
            End If
        Else
            If tv.HitTest(x, y) Is Nothing Then
            'do nothing
            Else
                'Highlight the node the mouse is over
                Set nodOver = tv.HitTest(x, y)
                Set tv.DropHighlight = nodOver
            End If
        End If
    
    End Sub
    
    
    Private Sub TreeView0_OLEDragDrop(Data As Object, _
                                        Effect As Long, _
                                        Button As Integer, _
                                        Shift As Integer, _
                                        x As Single, _
                                        y As Single)
    
        Dim sourceNode As MSComctlLib.Node
        Dim SourceParentNode As MSComctlLib.Node
        Dim targetNode As MSComctlLib.Node
        
        Dim tmpRootNode As MSComctlLib.Node
        Dim strtmpNodKey As String
        Dim ChildNode As MSComctlLib.Node
        
        Dim strSPKey As String
        Dim strTargetKey As String
        
        Dim strsQL As String
        Dim intKey As Integer
        Dim intPKey As Integer
        
        On Error Resume Next
        
        Select Case Screen.ActiveControl.Name
                
               Case TreeView0.Name
                    Set sourceNode = tv.SelectedItem
                
        End Select
        
        'Get Source Parent Node & Target Node Reference
        Set SourceParentNode = sourceNode.Parent
        Set targetNode = tv.HitTest(x, y)
                
        'If any errors then exit
        If Err <> 0 Then
            MsgBox Err & " : " & Err.Description, vbInformation + vbCritical, "OLEDragDrop()"
            Err.Clear
            Exit Sub
        Else
            On Error GoTo 0
        End If
        
    
        'Get/define Source parent Node Key to compare it with Target Node Key
        If SourceParentNode Is Nothing Then
            strSPKey = "Empty"
        Else
            strSPKey = SourceParentNode.Key
        End If
        
        'Check the Target Node/Location and define the Key
         Select Case True
            Case targetNode Is Nothing
                strTargetKey = "Empty"
            
            Case targetNode.Key = ""
                strTargetKey = "Empty"
                Set targetNode = Nothing
            Case Else
                strTargetKey = targetNode.Key
         End Select
        
        'Make sure the Target Node is not the source Node's own parent
        If strTargetKey = strSPKey Then Exit Sub
        
        'Track User's Node move action, check for error.
        On Error Resume Next
        
        If targetNode Is Nothing Then
            
            'If target Node is Nothing (the Node dropped in the empty area),
            'then the Node must be moved to the Root-level
            'save the original sourceNode.Key
            strtmpNodKey = sourceNode.Key
            
            'Modify the source Node Key, with addition of some text, say 'Empty', like 'X5Empty'
            'So that a temporary Node can be created with the original source Node key.
            'Note: Two Nodes with the same Key cannot remain in memory at the same time.
            'The Source Node with key 'X5Empty' deleted later,
            'temporary Node takes it's droped location.
            sourceNode.Key = sourceNode.Key & strTargetKey
    
            'Create the temporary Root Node, with original sourceNode Key
            Set tmpRootNode = tv.Nodes.Add(, , strtmpNodKey, sourceNode.Text)
            
            'define the Root Node image indexes
            'With tmpRootNode
            '    .Image = 1
            '    .SelectedImage = 4
            'End With
            
            'Move all child Nodes from SourceNode,if any,
            'as tmpRootNode's Children
            Do Until sourceNode.Children = 0
                Set sourceNode.Child.Parent = tmpRootNode
                
                'modify Node image indexes
                'With sourceNode
                '    .Image = 2
                '    .SelectedImage = 3
                'End With
            Loop
    
            'Delete the Source Node with modified Key from TreeView
            tv.Nodes.Remove sourceNode.Index
            
            'Move the tmpRootNode with original Key
            'to the dropped location on TreeView
            Set sourceNode = tmpRootNode
        Else
            'Move the sourceNode under targetNode as child
            Set sourceNode.Parent = targetNode
            
            'modify Node image indexes
            'With sourceNode
            '    .Image = 2
            '    .SelectedImage = 3
            'End With
        End If
        
        'Notify, if there was an Error then Exit, else Update PrentID of related Record.
        If Err <> 0 Then
            MsgBox Err & " : " & "Unable to move:" & vbCrLf & Err.Description, vbInformation + vbCritical, "DragDrop2()"
            Exit Sub
        Else
            'Build and execute the SQL statement to update the record
            If targetNode Is Nothing Then
                intKey = Val(Mid(sourceNode.Key, 2))
                strsQL = "UPDATE Sample SET ParentID = Null" & _
                         " WHERE ID = " & intKey
            Else
                intKey = Val(Mid(sourceNode.Key, 2))
                intPKey = Val(Mid(targetNode.Key, 2))
                
                strsQL = "UPDATE sample SET ParentID = " & intPKey & _
                         " WHERE ID = " & intKey
            End If
            
            'Modify the table records
            CurrentDb.Execute strsQL, dbFailOnError
            
            'If an error raised then refresh TreeView and exit
            If Err <> 0 Then
                MsgBox Err & " : " & Err.Description
                LoadTreeView 'Refresh/display TreeView without changes
            Else
                'Sort Nodes
                If sourceNode.Parent Is Nothing Then
                    sourceNode.Root.Sorted = True
                Else
                    sourceNode.Parent.Sorted = True
                End If
                
                tv.Nodes(sourceNode.Key).Selected = True
            End If
        End If
        On Error GoTo 0
    
    End Sub
    
    Private Sub TreeView0_OLECompleteDrag(Effect As Long)
    
        'Turn off the drophighlight
        Set tv.DropHighlight = Nothing
    
    End Sub
    
    Private Sub Form_Close()
    
    Set tv = Nothing
    End Sub

  6. For the drag-and-drop action, four event procedures are executed automatically. These handle the different stages of the process—when you begin dragging a node, when the dragged node is moved over other nodes (highlighting them as potential targets), and finally, when it is dropped either on another node or onto the empty root-level area.

    The Main Subroutines of the Code.

    The drag-and-drop operation in the TreeView control is managed through four key event procedures:

    • TreeView0_OLEStartDrag() – Initializes the drag process by setting the selected item and resetting the Node to Nothing.

    • TreeView0_OLEDragOver() – Functions like the MouseMove event; it highlights the node as the dragged item is moved over potential target nodes.

    • TreeView0_OLEDragDrop() – Handles the main task: performs the necessary checks, positions the node(s) at the drop location, and updates the related record in the base table.

    • TreeView0_OLECompleteDrag() – Completes the action by resetting the DropHighlight Property to Nothing.


Technically, the entire drag-and-drop process could be managed with the TreeView0_OLEDragDrop() procedure alone. However, without the supporting routines, nodes will not be visually highlighted when the dragged item passes over them. The only indicator would be the mouse pointer, which changes to display a small second arrow, as shown in the sample image above.

With that in mind, let us now focus on this subroutine and review its code in detail. At the beginning of the procedure, we declare the required string variables for the nodes, along with other supporting variables.

Rather than analyzing the code line by line here, note that each section is already documented with inline comments, making it easier for you to follow and understand the logic as you go through the code yourself.

The Sequence of Drap Drop Events

Let us first understand the sequence of events: the user selects a node, drags it across other nodes on the way to its final destination, and finally drops it either on a target node or in the empty space of the TreeView control to make it a root-level node.

As you drag a node over another node’s text, the text becomes highlighted, indicating your current position. Once you move away, the highlight disappears. This visual feedback continues until you reach the target node. The TreeView0_OLEDragOver() subroutine manages this highlighting behavior.

When a node is finally dropped, the TreeView0_OLEDragDrop() subroutine takes over. At this stage, the user’s intention must be interpreted, and the appropriate action taken. To move the node correctly, the following information must be captured and analyzed.

The Important Information to Keep Track Of.

To correctly process a drag-and-drop action, the following details must be identified and analyzed:

  • Source Node details: reference to the node, its key, ParentID value, and any child nodes.

  • Target Node or location: reference and key of the node on which the source node is dropped.

  • Drop on empty TreeView area: if the target is not another node but the blank area of the TreeView, the source node is moved to the root level.

  • Drop on another node: the target node becomes the new parent of the source node.

  • Child nodes: if the source node has children, they are moved together with their parent node.

Special cases where no action is taken:

  • Dropping a node onto its own parent.

    • Example: dragging the TextBox node and dropping it onto Controls, or dragging Controls onto its parent Form. These attempts simply place the node back in its current position, so the action is ignored.

  • Dragging a root-level node and dropping it onto the empty TreeView area. Since it is already a root node, no action is needed.

For all valid moves, the corresponding record in the Sample table must be updated by setting its ParentID field to reflect the new hierarchy.

Node Drop in the Root-level Empty Area.

In case #3 (dropping a node onto the empty TreeView area), we face a technical issue:

A new root-level node must be created with the same key as the source node. However, duplicate keys are not allowed in the TreeView hierarchy. This is where the procedure gets slightly confusing.

The workaround is as follows:

  1. Rename the source node’s key temporarily

    • Append a suffix to the key (e.g., change X5X5Empty) so that the original key (X5) becomes available.

    • This prevents a duplicate key conflict.

  2. Create a temporary node with the original key

    • Insert a new node using the original key (X5) at the root level.

  3. Reassign child nodes (if any)

    • Move all child nodes from the renamed source node (X5Empty) to the new temporary node (X5).

  4. Delete the renamed source node

    • Remove the source node (X5Empty) from the TreeView control.

    • Note: this deletion only affects the TreeView; the related record in the Sample table remains untouched.

  5. Promote the new node to the root level

    • The temporary node (X5) — now carrying its children — is placed at the root level.

  6. Update the table

    • Set the ParentID field of the corresponding record to a zero-length string (""), marking it as a root-level node in the database.


👉 This sequence ensures:

  • No duplicate key errors in the TreeView.

  • All child nodes remain intact with their parent.

  • The database correctly reflects the hierarchy change.

Self-Experiments of Drag and Drop.

You can try out a few drag-and-drop experiments yourself to see this in action. Select a node, hold down the left mouse button, and drag it to a new location—either onto another node or into the empty area of the TreeView control.

  • As you drag the node over another node’s text, the target node becomes highlighted, and the highlight disappears once you move away.

  • When you release the mouse button, the dragged node is placed in its new position.

  • You can repeat this process with a single node or a node with its children, and the entire branch will move together.

Behind the scenes, each move updates the related record in the table: the ParentID field is assigned the Key value (ID) of the new parent node’s record. If the node is dropped in the empty area, the ParentID is set to a zero-length string to mark it as a root-level node.

Why Two-Step Node Populating Procedure?

Now, let’s return to the LoadTreeView() subroutine and take a closer look at the two-step process we used to populate the TreeView control with all nodes.

  1. First pass: All records from the Sample table are added as root-level nodes, using the ID field value as the node key.

  2. Second pass:

    • If the ParentID field is empty, the node remains as a root-level node.

    • If the record contains a ParentID value, the node is correctly positioned under its corresponding parent node.

At this point, you may wonder: Why is this two-step process necessary?

Rather than explaining in detail, we’ll answer this through a quick experiment. You may already have tried a few drag-and-drop operations and rearranged nodes, which also updated their corresponding ParentID values in the table. To ensure our demo starts with clean data, we need to restore the table to its original state.

Fortunately, we already created a backup copy earlier, named Sample_bk. Here’s what to do:

  • Delete the current Sample table.

  • Create a fresh copy of Sample_bk and rename it as Sample.

  • Open the table to view the records and their ParentID values.

A sample image of the table is shown below:

The ID field values in the Sample table are AutoNumbers. They are always sequential and unique.

When adding child nodes to the TreeView control, we follow one fundamental principle:

The Simple Child Node Rule

A record’s ParentID value (the parent key) assumes that a parent node with the same Node-Key (ID) already exists in the TreeView control.

Let’s look at a couple of examples from the table image above:

  • Record 3:

    • ID = 3

    • ParentID = 2
      This means the record with ID = 2 must already exist in the TreeView before record 3 can be added as its child node.

  • Record 21:

    • ID = 21

    • ParentID = 12
      Even though record 21 appears much later in the table, its parent node (ID = 12) must still be present in the TreeView before it can be added as a child.

In both cases, the program assumes that by the time a record with a ParentID value is processed, the parent record (ID = ParentID) has already been added as a node in the earlier cycle of populating the TreeView.

Justifying the Two-Step Procedure.

Now let’s try out some drag-and-drop trial runs.

Before we begin, recall that we originally had a form named frmSample (from our first tutorial session). In that form, all TreeView nodes were loaded in a single pass. We’ve followed the same approach so far, but from this point onward, we’ll be shifting to the new method.

  1. Open frmSample

    • This form still uses the old loading method.

    • Observe how the TreeView nodes are displayed with the Sample table records.

  2. Close frmSample after you’re done viewing the nodes.

  3. Open frmDragDrop

    • This form is prepared for testing drag-and-drop functionality.

    • Select the node with the Node-Text “Table”.

    • Click and hold the left mouse button, drag it, and drop it onto the node with the Node-Text “Form.”

    👉 When you do this, the Table node and all of its children (its immediate child node Fields and the Fields’ own children) are moved under the Form node as child nodes.

  4. Close frmDragDrop and reopen it.

    • You will notice that the nodes are correctly positioned where you dropped them earlier, as shown in the sample image below.


✨ This step-by-step trial shows that the drag-and-drop action not only repositions nodes visually but also updates their relationships in the underlying Sample table so that the new hierarchy persists.

  1. Now, close the form frmDragDrop.

  2. Open the form frmSample.

    • Instead of displaying the TreeView nodes, you will be greeted with an error message:

      Element Not Found
      Error Number: 35601

  3. Click the Debug command button in the error dialog.

    • This takes you to the highlighted code line where the error occurred.

    • If you hover the mouse over the parameters in the Add() method:

      • nodKey shows X3

      • ParentKey shows X7

  4. From these values, we can infer:

    • The program is currently processing the record with ID = 3.

    • It is attempting to make this record a child node of the record with ID = 7.

    • However, node ID = 7 has not yet been added to the TreeView in this pass, which causes the Element Not Found error.

  5. Press the F5 key to bring the error dialog up again, and then click End to stop the program.

    • This will return you to the database window.

    • Close the form frmSample.

  6. Now, open the Sample table to review the arrangement of the ParentID values after our drag-and-drop action.

    • You will see the updated records reflecting the change.

    • Notice the highlighted record where the ParentID = 7, showing its parent record’s position.

    • This is the record that triggered the error when the TreeView tried to rebuild the hierarchy in frmSample.


  7. ✨ This illustrates why our two-step loading method LoadTreeView() (root-level first, then re-parenting in the second pass) is essential. It ensures that when a child node is being added, its parent already exists in the TreeView control.

    Following the normal node-population procedure, we arrive at the third record.

    • This record has a ParentID value of 7, which means the node with ID = 7 must already exist in the TreeView control.

    • However, since the node with ID = 7 has not yet been added, the program attempts to reference a non-existent node, which immediately triggers the error.

    You might think that simply sorting the records  ParentID before loading them could resolve the issue. Unfortunately, even if we rearrange the records this way, the problem remains. The new order would look like the sample image shown below.


    ✨ This sets up the explanation for why a two-pass method is necessary—because sorting alone cannot guarantee that every parent node exists before its children are added.

    When we look further into the recordset, we find another case where the expected Parent Node is not yet available in the TreeView. This reinforces the fact that relying on a single-pass load (or even sorting) is not enough.

    This is exactly why our two-step TreeView Node loading approach is necessary. It works consistently for both the initial load and for cases where Nodes have been rearranged by drag-and-drop.

    Step 1 – Populate all records as Root-level Nodes

    • Each record is added to the TreeView as a root-level Node.

    • The record’s ID field value is used as the Node-Key.

    • At this point, all Nodes exist in the TreeView, even though the hierarchy is not yet correct.

    With this, we guarantee that no matter what ParentID a record refers to, the corresponding Node already exists in the TreeView.

    Step 2 – Reorganize Nodes under their correct parents

    • Loop through the same set of records again.

    • If the ParentID field is empty, the Node remains at the root level.

    • Otherwise, the Node is reassigned as a child of its Parent Node using the following statement:

    Set tv.Nodes.Item(strKey).Parent = tv.Nodes.Item(strPKey)

    Here,

    • strKey = current record’s Node Key (ID),

    • strPKey = ParentID value.

    This is achieved by resetting the record pointer with:

    rst.MoveFirst

    and then looping through all records again with the usual pattern:

    Do While Not rst.EOF '… Node reassignment logic … rst.MoveNext Loop

    ✨ The key benefit of this two-pass approach is that it guarantees parent Nodes exist before child reassignment. This avoids the “Element Not Found” error entirely and ensures the TreeView hierarchy always reflects the current state of the data—even after drag-drop operations have reshaped the relationships.

    Second Step in Reverse Order.

    Alternatively, you can approach the second pass in reverse order.

    After all records have been populated as Root-level Nodes, the record pointer will naturally be positioned at the end of the recordset (EOF). From there:

    1. Reset the pointer to the last record using:

      rst.MoveLast
    2. Begin a loop with a BOF check, and for each iteration, move the pointer backwards with:

      rst.MovePrevious
    3. Within the loop, reassign each Node under its Parent Node as required.

    This method ensures that every Node has already been created in the TreeView when its children are processed—just like in the forward pass. However, you may notice that the visual placement order of Nodes in the TreeView differs slightly compared to the forward-loading method.

    I encourage you to try this reverse-order approach yourself with the code changes above and observe the results.

    Download Demo Database.


    1. MS-Access Class Module and VBA
    2. MS-Access VBA Class Object Arrays
    3. MS-Access Base Class and Derived Objects
    4. VBA Base Class and Derived Object-2
    5. Base Class and Derived Object Variants
    6. MS-Access Recordset and Class Module
    7. Access Class Module and Wrapper Classes
    8. Wrapper Class Functionality Transformation

        Share:

        TreeView ImageCombo Drop-Down Access Menu

        Introduction.

        In this session of the TreeView Control Tutorial, we will focus on programming the ImageComboBox Control. Our objective is to build an MS Access project drop-down menu using an ImageCombo control. Additionally, we will use a second ImageComboBox control to display the images along with their key values from the ImageList control. Both ImageComboBox controls will draw their images from a common ImageList control, into which the images were manually uploaded from the computer during an earlier session of this tutorial series.

        The sessions of the TreeView Control Tutorial covered so far are as follows:

        The TreeView Control Tutorial Session Links.

        1. Microsoft TreeView Control Tutorial
        2. Creating an Access Menu with a TreeView Control
        3. Assigning Images to TreeView Control
        4. Assigning Images to TreeView Control-2
        5. TreeView Control Check-Mark Add, Delete Nodes

        The Demo View of Both ImageComboBoxes Expanded.

        The completed MS-Access Project Drop-Down Menu Image is given below:

        The Design View Image of the above Form is given below:

        The Drop-Down ImageComboBox Source Data.

        The source data for the new MS Access Project Drop-Down Menu is taken from our earlier Access Menu Project.

        If you have not already done so, please download the demo database using the link provided in item 4 above. Once downloaded, you will have all the required data objects to proceed with this session.

        The database includes three tables: Categories, Products, and Menu. It also contains two forms to display category and product data, along with a parameter form for filtering report data.

        Additionally, we will utilize two forms—frmMenu and frmMenu2—which were introduced in our earlier tutorial sessions.

        You will also find two reports for previewing the Categories and Products data items.

        In addition, there are two macros designed to display simple messages. These macros can also be used to sequence action queries for data processing, particularly when working with more complex reports. In earlier tutorial sessions, we executed these actions by selecting options from the TreeView Control 

        Project Menu.

        We will also need these objects here, as we are about to create a new drop-down menu using the ImageComboBox Control. The goal is to ensure that all objects can be opened through selections in the new drop-down menu, in the same way we accessed them earlier on the frmMenu2 form using the TreeView Control within this same database.

        The Menu table image is given below for your reference.

        Preparing for the Design of the Drop-Down Menu Form.

        Open the Menu Form in Design View, and you will see two menu-related controls:

        • An ImageList control

        • An ImageComboBox control

        Additionally, another ImageComboBox control has been placed on the right side of the form to display the images stored in the ImageList.

        To set this up:

        1. Copy the ImageList Control

          • Open the frmMenu2 form, copy the ImageList control, and paste it onto a new form named frmMenu3Combo.

          • This ImageList control already contains images that were manually uploaded from the computer in an earlier tutorial session.

          • Open the Property Sheet: right-click the ImageList control, select ImageListCtl Object, and then choose Properties to review the images and their key names.

        2. Add the First ImageComboBox Control

          • From the ActiveX Controls group, insert a Microsoft ImageComboBox Control onto the left side of the form.

          • Rename this control to imgCombo1. This will serve as the drop-down menu.

        3. Add the Second ImageComboBox Control

          • Insert another ImageComboBox control on the right side of the form.

          • Rename this control to imgCombo2. This control displays the images and their key names from the ImageList0 control in a drop-down list.

        4. Add a Label

          • Place a label above the second ImageComboBox control.

          • Set its caption to Image List.

        The Images Listed in the Image Combo Box control.

        We will begin with the second control, imgCombo2, which is used to display the list of images from the ImageList control. Once you understand this code, creating the drop-down menu will become much easier.

        The VBA code for the frmMenu3Combo form module has been divided into two parts. Let us start with the first part and examine its contents.

        In the global declaration area, the main object variables are defined. The Form_Load() event procedure initializes the ImageList control on the form and assigns it to the object variable 'objImgList'. It then calls the cboImageList() subroutine, which loads the images from the ImageList control into the second ImageComboBox control (imgCombo2).

        Now, let’s take a closer look at the code.

        The first-part vba code, with the Form_Load() and cboImageList() subroutines listed below:

        Dim imgcombo1 As MSComctlLib.ImageCombo
        Dim imgCombo2 As MSComctlLib.ImageCombo
        Dim objimgList As MSComctlLib.ImageList
        Const KeyPrfx As String = "X"
        
        Private Sub Form_Load()
        
        Set objimgList = Me.ImageList0.Object
        
        cboImageList 'load imagelist-combo
        
        'CreateMenu 'Create Drop-Down Menu
         
        End Sub
        
        Private Sub cboImageList()
        Dim j As Integer
        Dim strText As String
        
        Set imgCombo2 = Me.ImageCombo2.Object
        imgCombo2.ImageList = objimgList
        
        For j = 1 To objimgList.ListImages.Count
            strText = objimgList.ListImages(j).Key
            imgCombo2.ComboItems.Add , , strText,j,,j
        Next
            imgCombo2.ComboItems(1).Selected = True
        End Sub
        

        VBA Code Review.

        In the global declaration area, we have defined the following variables:

        • imgCombo1 – the ImageComboBox control used for the project menu.

        • imgCombo2 – the ImageComboBox control used for displaying images from the ImageList control.

        • objImgList – an object variable representing the ImageList control on the form.

        • KeyPrfx – a constant variable assigned the character "X".

        Within the Form_Load() event procedure, the objImgList variable is initialized with the ImageList control on the form using the following statement:

        Set objImgList = Me.ImageList0.Object

        This allows all pre-loaded images in the ImageList control to be accessed through the objImgList object.

        Next, the procedure calls the cboImageList() subroutine, which adds all the images to the imgCombo1 control.

        For now, the call to the CreateMenu() subroutine has been commented out.

        Inside the cboImageList() subroutine, two variables are declared.

        Next, the following statement assigns the second ImageComboBox control on the form to the object variable imgCombo2:

        Set imgCombo2 = Me.ImageCombo2.Object

        Similar to the TreeView control, the imgCombo2 control includes an ImageList property, which is used to link it with the ImageList control. This allows the ImageComboBox to access the properties of the ImageList. The statement below establishes that link:

        imgCombo2.ImageList = objImgList

        After this, a 'For…Next loop' is executed to iterate through the collection of images contained in the ImageList control. Each image is then processed and added to the imgCombo2 drop-down list.

        The first item from the ImageList has its key value ("form_close") stored in the variable strText. In this example, we are using the Key value of the ImageList item as the text (or description) for the corresponding image in the ImageComboBox control.

        Since no descriptive text is available apart from the key, it serves as the most suitable option. The Tag property, on the other hand, is left empty because it is for a different purpose later when working with the drop-down menu.

        The next statement is an important one that we need to examine closely: the Add method of the ImageComboBox control. This method is used to add items (with images) to the ImageComboBox list.

        The general syntax of the statement is as follows:

        imgCombo2.ComboItems.Add [Index],[Key],[Text],[Image],[SelImage],[Indentation]

        All the parameters of the Add() method are optional. For our initial test run of this control, we will supply values only for the [Text], [Image], and [Indentation] parameters.

        After previewing the result of this first test run (the image list view), we will discontinue using the [Indentation] parameter for this ImageCombo control, since it is not required for our intended design.

        Note: Keep in mind that we will later need the Indentation property when creating the drop-down menu. This will allow the menu items to visually resemble Root Nodes and Child Nodes, just as they appear in the TreeView control.

        At that stage, we will also make use of the [Key] parameter—assigning it the same value as the Text parameter—so that we can reliably access a specific menu item’s Tag property value.  

        Image List with incrementing Indentation Param Setting.

        The result of the first test run, displaying the image list in imgCombo2, appears as shown in the illustration below. This output is achieved by applying incremental values to the Indentation property for each item.

        From this trial run, the effect of indentation is clearly visible: each successive item is shifted slightly to the right, one step further than the previous one. This feature is useful for positioning our project menu items so that they visually resemble Root-Level and Child Nodes, similar to the structure in the TreeView control.

        After the strText value ("form_close"), The first variable j refers to the ImageList’s index number. The [SelImage] parameter is skipped in our test, and the next occurrence of j is used to set the Indentation level for each list item when displayed in the ComboBox.

        For the initial test run, after reviewing the output, you may remove all parameters that come after the image index value, as they are not required for our purpose.

        The next statement is:

        imgCombo2.ComboItems(1).Selected = True

        This selects the first item in the ComboBox. When the Combobox item is selected programmatically  (through code), the Change() event of the ImageCombo control is triggered. However, when an item is selected manually on the form, this event is not fired. To address this, the Update() event is used; it ignores manual updates and instead attempts to invoke the event explicitly through code.

        Save the form frmMenu3Combo and open it in Normal View.  Expand the second ImageList ComboBox control and view the result.  Remove the commas and the variable < j > at the end, after the first variable < j >, retained for the ImageList index number.

        The VBA Code of the Project Drop-Down Menu.

        Now, let us move on to the second part of the form module VBA code, where we will learn how to create the Access Drop-Down Menu. In this section, we will also see how to open Forms, Reports, and Macros by selecting an item from the ComboBox control.

        The second part of the VBA code consists of two main procedures:

        1. CreateMenu() – a subroutine that builds the project drop-down menu by adding menu items to the ImageComboBox control.

        2. ImageCombo1_Click() – the event procedure that responds when a user selects a menu option, triggering the opening of the corresponding Access object.

        The code for these procedures is shown below:

        Private Sub CreateMenu()
        Dim db As DAO.Database
        Dim rst As DAO.Recordset
        Dim strSQL As String
        Dim strKey As String
        Dim strText As String
        Dim typ As Integer
        
        Set imgcombo1 = Me.ImageCombo1.Object
        imgcombo1.ImageList = objimgList
        
        strSQL = "SELECT ID, Desc, PID, Type,Macro,Form,Report FROM Menu;"
        
        Set db = CurrentDb
        Set rst = db.OpenRecordset(strSQL, dbOpenDynaset)
        
        Do While Not rst.EOF And Not rst.BOF
            If Len(Trim(Nz(rst!PID, ""))) = 0 Then
                strKey = KeyPrfx & CStr(rst!ID)
                strText = rst!Desc
                imgcombo1.ComboItems.Add , strKey, strText, 1, 2, 1 ' image index 1,2([image],[selectedimage])
                'imgcombo1.ComboItems.Add , strKey, strText, "folder_close", "folder_open", 1
            Else
                strKey = KeyPrfx & CStr(rst!ID)
                strText = rst!Desc
                imgcombo1.ComboItems.Add , strKey, strText, 4, 5, 4 'last param is spacing
                'imgcombo1.ComboItems.Add , strKey, strText, "left_arrow", "right_arrow", 4
             
                'Check for the presense of Type Code
                If Nz(rst!Type, 0) > 0 Then
                        typ = rst!Type
                        With imgcombo1.ComboItems
                    Select Case typ
                        'save type Code & Form/Report/Macro Name in Tag Property
                        Case 1
                            .Item(strKey).Tag = typ & rst!Form
                        Case 2
                            .Item(strKey).Tag = typ & rst!Report
                        Case 3
                            .Item(strKey).Tag = typ & rst!Macro
                    End Select
                        End With
                End If
                
            End If
            rst.MoveNext
        Loop
        rst.Close
        imgcombo1.ComboItems.Item(1).Selected = True
        End Sub
        
        Private Sub ImageCombo1_Click()
        Dim strObject As String
        Dim strTag As String
        Dim typ As Integer
        
        strTag = ImageCombo1.SelectedItem.Tag
        typ = Val(strTag)
        strObject = Mid(strTag, 2)
        
        Select Case typ
            Case 1
                DoCmd.OpenForm strObject, acNormal
            Case 2
                DoCmd.OpenReport strObject, acViewPreview
            Case 3
                DoCmd.RunMacro strObject
        End Select
        
        End Sub
        

        Before diving into the VBA code, take a moment to review the Table Image (the third image from the top of this page), especially if you have not already done so in the earlier session on Access Project Menu creation.

        The table fields are defined as follows:

        • ID – An AutoNumber field that provides a unique identifier for each record.

        • Desc – Contains either the object type group names (Forms, Reports, Macros) or the actual object names of forms, reports, and macros.

        • PID (Parent ID) – Empty for the object group names (Forms, Reports, Macros). These empty PID values ensure that the group names appear at the leftmost position in the ImageComboBox drop-down menu, with one-character space indentation. All other items (child objects) are indented by four character spaces.

        This indentation technique makes the menu items visually resemble Root-Level and Child-Level Nodes, similar to the structure in a TreeView control—except that the connecting tree lines will not be displayed.


        In the ImageComboBox control, the selected item’s image is positioned at the leftmost side. The Group items (such as Forms, Reports, Macros) appear with a one-character indentation, while the child items under each group are indented by four character spaces.

        The PID field plays an important role in this arrangement.

        • If the PID field value is empty, the record is treated as a Group Name (e.g., Forms, Reports, Macros).

        • If the PID field contains a value, the record is treated as an Access Object Name, which must be opened when the user clicks it. These items are displayed as child members of their respective groups.

        Although the actual key values in the PID field are not significant in this context, we need the field to determine the hierarchy. Alternatively, the Type field can also serve this purpose.

        The Type field contains the object type codes:

        • 1 – Form

        • 2 – Report

        • 3 – Macro

        Based on this code, the next three fields—Form, Report, and Macro—store the actual object names. For clarity in design, these names have been kept in separate fields, though they could also be stored in a single column.

        The Type Code and Object Name pair (say 2rptCategories) will be saved in the ImageComboBox’s Tag Property.

        The CreateMenu() Subroutine.

        Now, let’s move on to the VBA code of the CreateMenu() subroutine.

        At the start of the procedure, the Database and other working variables are declared.

        1. The imgCombo1 object variable is initialized with the Me.ImageCombo1 control on the form.

        2. The 'imgCombo1.ImageList' property is then assigned the reference of the objImgList object, enabling direct access to the ImageList’s Index numbers and Key values.

        3. The Menu table is opened as a recordset using an SQL string.

        4. For each record, the PID field is checked:

          • If PID is empty, the record is treated as an object group name (e.g., Forms, Reports, Macros).

          • In this case, the ID value is prefixed with the constant "X" and stored in the variable strKey, while the Desc field value is stored in strText.

        5. The Add() method of the ImageComboBox control is then called, and the first item is added to the drop-down menu.


        imgcombo1.ComboItems.Add , strKey, strText, 1, 2, 1

        The first parameter (Index) is omitted—Access assigns it automatically.

        • Key: strKey contains the ID value prefixed with the constant X (e.g., X1).

        • Text: strText holds the Desc field value.

        • Image / SelImage: 1 is the index (or "folder_close" key) of the first ImageList image; 2 is the index (or "folder_open" key) of the second image.

        • Indentation: 1 Indents the item one level.

        Example:

        ' Add a group item (e.g., "Forms") imgCombo1.ComboItems.Add , strKey, strText, 1, 2, 1 ' …or with image keys: ' imgCombo1.ComboItems.Add , strKey, strText, "folder_close", "folder_open", 1

        Note: To confirm image index order, refer to the ImageCombo2 display you created earlier. If you like, you can also prepend the image index to the Text (not the Key) for clarity—for example: "1 form_close", "2 form_open".

        @@@If the PID field value is non-zero, then the record represents an actual menu option (not a group header). In this case, the item is added under the Else clause.

        The process is similar to how we added the group items earlier, but with a few differences:

        • For the [Image] and [SelImage] parameters, we use the ImageList item index values 4 and 5.

        • The Indentation parameter is set to 4 character spaces, so that these items appear as child members under their respective group headings.

        Example:

        ' Add a child item (e.g., an actual Form, Report, or Macro) imgCombo1.ComboItems.Add , strKey, strText, 4, 5, 4 

        This ensures that the group names (e.g., Forms, Reports, Macros) stay at the root level, while their associated objects are properly indented beneath them, visually simulating a TreeView hierarchy in the ImageComboBox.

        Within the ImageCombo item’s Add() method, under the Else clause, we also need to store the Access Object Name along with its Type Code in the item’s Tag property.

        For example:

        ' Save object details (Form Name + Type Code) in the Tag property ImageCombo1.ComboItems.Item(strKey).Tag = "frmData Entry;1"

        Here:

        • "frmData Entry" is the object name (in this case, a form).

        • 1 is the Type Code, which identifies it as a form (2 = report, 3 = macro).

        When the user selects this item from the drop-down menu, the Click() event of the ImageComboBox fires. In this event procedure:

        1. The Tag property value of the selected item is retrieved.

        2. The value is split into the object name and the Type Code.

        3. The Type Code is checked:

          • If it equals 1, the item is a form, and the form name is opened using:

          DoCmd.OpenForm "frmData Entry"

        This same logic will later be extended for Reports and Macros, using their respective Type Codes.

        @@@With this, all the records from the Menu Table are successfully added to the ImageComboBox control.

        The statement:

        imgCombo1.ComboItems.Item(1).Selected = True

        sets the first item as the default selection in the ImageComboBox. When this line of code executes, the Change() event is triggered; however, selecting an item directly in Form View does not fire the Change event.

        Note: Before running the form to test the drop-down menu, make sure to remove the comment symbol from the CreateMenu call in the Form_Load() event procedure. This line was commented out earlier during trial runs when we were testing image display in the ImageCombo2 control.

        The ImageCombo1_Click() event fires whenever the user selects an item from the drop-down menu. In this procedure, the selected item’s Tag property is parsed to retrieve the Type Code and the Object Name, and the corresponding Access object is opened using:

        DoCmd.ObjectType ObjectName

        Finally, for your reference and practice, the Demo Database (ProjectMenuV221.accdb) is provided in .zip format for download.

        DICTIONARY OBJECT

        1. Dictionary Objects Basics
        2. Dictionary Object Basics-2
        3. Sorting Dictionary Object Keys and Items
        4. Display Records from Dictionary
        5. Add Class Objects as Dictionary Items
        6. Update Class Object Dictionary Item

        Share:

        PRESENTATION: ACCESS USER GROUPS (EUROPE)

        Translate

        PageRank

        Post Feed


        Search

        Popular Posts

        Blog Archive

        Powered by Blogger.

        Labels

        Forms Functions How Tos MS-Access Security Reports msaccess forms Animations msaccess animation Utilities msaccess controls Access and Internet MS-Access Scurity MS-Access and Internet Class Module External Links Queries Array msaccess reports Accesstips WithEvents msaccess tips Downloads Objects Menus and Toolbars Collection Object MsaccessLinks Process Controls Art Work Property msaccess How Tos Combo Boxes Dictionary Object ListView Control Query VBA msaccessQuery Calculation Event Graph Charts ImageList Control List Boxes TreeView Control Command Buttons Controls Data Emails and Alerts Form Custom Functions Custom Wizards DOS Commands Data Type Key Object Reference ms-access functions msaccess functions msaccess graphs msaccess reporttricks Command Button Report msaccess menus msaccessprocess security advanced Access Security Add Auto-Number Field Type Form Instances ImageList Item Macros Menus Nodes RaiseEvent Recordset Top Values Variables Wrapper Classes msaccess email progressmeter Access2007 Copy Excel Export Expression Fields Join Methods Microsoft Numbering System Records Security Split SubForm Table Tables Time Difference Utility WScript Workgroup database function msaccess wizards tutorial Access Emails and Alerts Access Fields Access How Tos Access Mail Merge Access2003 Accounting Year Action Animation Attachment Binary Numbers Bookmarks Budgeting ChDir Color Palette Common Controls Conditional Formatting Data Filtering Database Records Defining Pages Desktop Shortcuts Diagram Disk Dynamic Lookup Error Handler External Filter Formatting Groups Hexadecimal Numbers Import Labels List Logo Macro Mail Merge Main Form Memo Message Box Monitoring Octal Numbers Operating System Paste Primary-Key Product Rank Reading Remove Rich Text Sequence SetFocus Summary Tab-Page Union Query User Users Water-Mark Word automatically commands hyperlinks iSeries Date iif ms-access msaccess msaccess alerts pdf files reference restore switch text toolbar updating upload vba code