VERSION 5.00
Object = "{831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0#0"; "mscomctl.ocx"
Begin VB.UserControl ArmTreeView 
   ClientHeight    =   6540
   ClientLeft      =   0
   ClientTop       =   0
   ClientWidth     =   4080
   EditAtDesignTime=   -1  'True
   ScaleHeight     =   6540
   ScaleWidth      =   4080
   ToolboxBitmap   =   "A_treevw.ctx":0000
   Begin VB.Frame frmFind 
      Height          =   1815
      Left            =   240
      TabIndex        =   1
      Top             =   120
      Visible         =   0   'False
      Width           =   3375
      Begin VB.ComboBox cmbLevel 
         Height          =   315
         ItemData        =   "A_treevw.ctx":0532
         Left            =   120
         List            =   "A_treevw.ctx":0534
         Style           =   2  'Dropdown List
         TabIndex        =   5
         Top             =   360
         Width           =   3135
      End
      Begin VB.TextBox txtFind 
         Height          =   285
         Left            =   120
         MaxLength       =   50
         TabIndex        =   4
         Top             =   960
         Width           =   3135
      End
      Begin VB.CommandButton cmdCancel 
         Caption         =   "&Cancel"
         Height          =   375
         Left            =   1920
         TabIndex        =   3
         Top             =   1320
         Width           =   1215
      End
      Begin VB.CommandButton cmdFindNext 
         Caption         =   "&Find next"
         Height          =   375
         Left            =   240
         TabIndex        =   2
         Top             =   1320
         Width           =   1215
      End
      Begin VB.Label lblText 
         Caption         =   "Text"
         Height          =   195
         Left            =   120
         TabIndex        =   7
         Top             =   720
         Width           =   1095
      End
      Begin VB.Label lblLevel 
         Caption         =   "Level"
         Height          =   195
         Left            =   120
         TabIndex        =   6
         Top             =   120
         Width           =   1095
      End
   End
   Begin MSComctlLib.TreeView TreeViewCtrl 
      Height          =   3855
      Left            =   240
      TabIndex        =   0
      Top             =   2040
      Width           =   3495
      _ExtentX        =   6165
      _ExtentY        =   6800
      _Version        =   393217
      HideSelection   =   0   'False
      Indentation     =   529
      LabelEdit       =   1
      LineStyle       =   1
      Style           =   7
      Appearance      =   1
      BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851} 
         Name            =   "Arial"
         Size            =   8.25
         Charset         =   0
         Weight          =   400
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
   End
   Begin MSComctlLib.ImageList ImageListCtrl 
      Left            =   240
      Top             =   5880
      _ExtentX        =   1005
      _ExtentY        =   1005
      BackColor       =   -2147483643
      MaskColor       =   16711935
      _Version        =   393216
   End
   Begin VB.Menu MenuPopup 
      Caption         =   ""
      Begin VB.Menu MenuFind 
         Caption         =   "Find..."
      End
      Begin VB.Menu MenuInfo 
         Caption         =   "More Info..."
         Begin VB.Menu MenuData 
            Caption         =   "Data"
            Index           =   0
         End
      End
      Begin VB.Menu MenuCheckBoxes 
         Caption         =   "Use checkboxes"
      End
      Begin VB.Menu MenuCheckAll 
         Caption         =   "Check all"
         Visible         =   0   'False
      End
      Begin VB.Menu MenuUnCheckAll 
         Caption         =   "UnCheck all"
         Visible         =   0   'False
      End
      Begin VB.Menu MenuSeparator 
         Caption         =   "-"
      End
      Begin VB.Menu MenuCount 
         Caption         =   "Count"
         Begin VB.Menu MenuCountChildren 
            Caption         =   "Children"
         End
         Begin VB.Menu MenuCountChecked 
            Caption         =   "Checked"
            Visible         =   0   'False
         End
         Begin VB.Menu MenuCountAll 
            Caption         =   "All"
         End
      End
   End
End
Attribute VB_Name = "ArmTreeView"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'HISTORY
'1.0.1  added Use checkboxes in popup menu,
'       Find for offline works
'       fixed bug in LoadTypeLevels
'       modified GridRequest as an array + SelectedNodeRequest + CheckedNodeRequests
'1.0.2  optimized min max
'1.0.6  fixed CheckedNodesRequests for offline - doesn't return array anymore
'1.0.7  checkboxes event
'1.0.9  Find dialog with levels
'1.1.2  New functionality to checkview, added access to iteminfo ...
'1.1.4  Fixed refresh - repositioning, remember state of checkboxes and expand
'1.1.5  Added Enabled property
'1.1.9  Optimized FindNodesInTree
'1.2.0  Added function ExpandNode for expanding node in treeview which is not already loaded

'17.2.2006  - Added RemoveNodeDuplicity flag
'20.3.2006  - new property FoundNodeInfo

Option Explicit

'windows API call from user32 to send message to a window (control)
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
                            (ByVal hwnd As Long, _
                            ByVal wMsg As Long, _
                            wParam As Any, _
                           lParam As Any) As Long   ' <--- !!


Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
 (ByVal hwnd As Long, ByVal nIndex As Long) As Long

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
 (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Private Const TVS_NOTOOLTIPS = &H80

Private Const GWL_STYLE = (-16)

'Standard size of array with node information - data from record
Const BASE_NODE_DATA_SIZE As Long = 3
Const TVI_STATE_NONE = 0
Const TVI_STATE_UNCHECKED = 7
Const TVI_STATE_CHECKED = 8
Const TVI_STATE_GRAYED = 9

'Windows message constant to enable or disable redrawing of control
Private Const WM_SETREDRAW = &HB

Private Const CH_PHOLDER = "$"
Private Const CH_LDELIMIT = "@"
Private Const SEP = "ой"                  'standard armstrong separator

'Tree load type enumeration
Private Enum UIStringConstants
  cstFindNext
  cstFind
  cstMoreInfo
  cstCancel
  cstLevel
  cstText
  cstAll
  cstCount
  cstCountAll
  cstCountChildren
  cstCountChecked
  cstCheckBoxes
  cstCheckAll
  cstUnCheckAll
  cstNotFound
End Enum

Private Enum TVM_GET_SETIMAGELIST_wParam
    TVSIL_NORMAL = 0
    TVSIL_STATE = 2
End Enum

Const Win32_IE = &H400

Private Type POINTAPI   ' pt
    X As Long
    Y As Long
End Type

Private Enum TVHITTESTINFO_flags
    TVHT_NOWHERE = &H1
    TVHT_ONITEMICON = &H2
    TVHT_ONITEMLABEL = &H4
    TVHT_ONITEMINDENT = &H8
    TVHT_ONITEMBUTTON = &H10
    TVHT_ONITEMRIGHT = &H20
    TVHT_ONITEMSTATEICON = &H40
    TVHT_ONITEM = (TVHT_ONITEMICON Or TVHT_ONITEMLABEL Or TVHT_ONITEMSTATEICON)
    
    TVHT_ABOVE = &H100
    TVHT_BELOW = &H200
    TVHT_TORIGHT = &H400
    TVHT_TOLEFT = &H800
End Enum

Private Type TVHITTESTINFO   ' was TV_HITTESTINFO
    pt As POINTAPI
    flags As TVHITTESTINFO_flags
    hItem As Long
End Type

' ================================================================
' TVITEM struct

Private Type tvItem   ' was TV_ITEM
    mask As TVITEM_mask
    hItem As Long
    state As TVITEM_state
    stateMask As Long
    pszText As Long   ' pointer
    cchTextMax As Long
    iImage As Long
    iSelectedImage As Long
    cChildren As Long
    lParam As Long
End Type

#If (Win32_IE >= &H400) Then
' only used for Get and Set messages (not for notifications)
Private Type TVITEMEX
    mask As TVITEM_mask
    hItem As Long
    state As TVITEM_state
    stateMask As Long
    pszText As Long   ' pointer
    cchTextMax As Long
    iImage As Long
    iSelectedImage As Long
    cChildren As Long
    lParam As Long
    iIntegral As Long
End Type
#End If

Private Enum TVITEM_mask
    TVIF_TEXT = &H1
    TVIF_IMAGE = &H2
    TVIF_PARAM = &H4
    TVIF_STATE = &H8
    TVIF_HANDLE = &H10
    TVIF_SELECTEDIMAGE = &H20
    TVIF_CHILDREN = &H40
#If (Win32_IE >= &H400) Then
    TVIF_INTEGRAL = &H80
#End If
    TVIF_DI_SETITEM = &H1000   ' Notification
End Enum
    
Private Enum TVITEM_state
    TVIS_SELECTED = &H2
    TVIS_CUT = &H4
    TVIS_DROPHILITED = &H8
    TVIS_BOLD = &H10
    TVIS_EXPANDED = &H20
    TVIS_EXPANDEDONCE = &H40
#If (Win32_IE >= &H300) Then
    TVIS_EXPANDPARTIAL = &H80
#End If
    
    TVIS_OVERLAYMASK = &HF00
    TVIS_STATEIMAGEMASK = &HF000
    TVIS_USERMASK = &HF000
End Enum

Private Enum TVMessages
    TV_FIRST = &H1100
   
    TVM_DELETEITEM = (TV_FIRST + 1)
    TVM_EXPAND = (TV_FIRST + 2)
    TVM_GETITEMRECT = (TV_FIRST + 4)
    TVM_GETCOUNT = (TV_FIRST + 5)
    TVM_GETINDENT = (TV_FIRST + 6)
    TVM_SETINDENT = (TV_FIRST + 7)
    TVM_GETIMAGELIST = (TV_FIRST + 8)
    TVM_SETIMAGELIST = (TV_FIRST + 9)
    TVM_GETNEXTITEM = (TV_FIRST + 10)
    TVM_SELECTITEM = (TV_FIRST + 11)
    
    #If UNICODE Then
      TVM_GETITEM = (TV_FIRST + 62)
      TVM_SETITEM = (TV_FIRST + 63)
      TVM_EDITLABEL = (TV_FIRST + 65)
    #Else
      TVM_GETITEM = (TV_FIRST + 12)
      TVM_SETITEM = (TV_FIRST + 13)
      TVM_EDITLABEL = (TV_FIRST + 14)
    #End If
    
    TVM_HITTEST = (TV_FIRST + 17)
    TVM_ENSUREVISIBLE = (TV_FIRST + 20)
End Enum   ' TVMessages

Private Enum TVM_GETNEXTITEM_wParam
    TVGN_ROOT = &H0
    TVGN_NEXT = &H1
    TVGN_PREVIOUS = &H2
    TVGN_PARENT = &H3
    TVGN_CHILD = &H4
    TVGN_FIRSTVISIBLE = &H5
    TVGN_NEXTVISIBLE = &H6
    TVGN_PREVIOUSVISIBLE = &H7
    TVGN_DROPHILITE = &H8
    TVGN_CARET = &H9
#If (Win32_IE >= &H400) Then
    TVGN_LASTVISIBLE = &HA
#End If
End Enum


'Tree load type enumeration
Public Enum TreeLoadType
  LoadTypeNone
  LoadTypeTree
  LoadTypeBranch
  LoadTypeLevel
  LoadTypeChilds
  LoadTypeChildsDemand
  LoadTypeChildsCount
  LoadTypeParent
  LoadTypeFindSQL
End Enum

'PUBLIC EVENTS
'fired when control is resized
Public Event Resize()
'fired when control is clicked
Public Event Click()
'fired when control is double clicked
Public Event DblClick()
'fired when node is collapsed
Public Event Collapse(ByVal Node As MSComctlLib.Node)
'fired when node is expanded
Public Event Expand(ByVal Node As MSComctlLib.Node)
'fired when key is down while focus on treeview
Public Event KeyDown(KeyCode As Integer, Shift As Integer)
'fired when key is pressed
Public Event KeyPress(KeyAscii As Integer)
'fired when key is up
Public Event KeyUp(KeyCode As Integer, Shift As Integer)
'fired when node is checked
Public Event NodeCheck(ByVal Node As MSComctlLib.Node)
'fired when node is clicked
Public Event NodeClick(ByVal Node As MSComctlLib.Node)
'fired when node is found or not found after calling Find, FindSQL or FindNext.
Public Event NodeFound(ByVal lb_Found As Boolean)
'fired when load action in treeview
Public Event TreeNodesLoad(ByRef LoadType As TreeLoadType, ByVal NodeInfo As NodeInfo, ByVal Level As Long, _
  ByRef Request As Variant, ByVal NewNodeInfo As NodeInfo)
Public Event TreeNodesLoaded(ByVal LoadType As TreeLoadType, ByVal NodeInfo As NodeInfo, ByVal Level As Long, ByRef Request As Variant)
Public Event TreeNodeAdded(ByVal ao_Node As Node)
Public Event BeforeNodeCheck(ByVal Node As MSComctlLib.Node, ByRef ab_Allow As Boolean)

'fired when node is clicked
Public Event CheckboxesChange(Checkboxes As Boolean)

'to check before cmdFindNext is processed   15.05.2006
Public Event FindNextClick(ByRef as_findText As String, ByRef ab_doSearch As Boolean)

'**************************************************************************************************************
'PRIVATE VARIABLES
'**************************************************************************************************************

'ID value of root node
Private mv_RootValue As Variant
'last load type for loading treeview content by LoadTree (used also for Refresh)
Private mut_TreeLoadType As TreeLoadType
'collection of nodes found in last call of Find or FindSQL (by using built in popup menu)
Private mo_FoundNodeInfos As Collection
'index of found node on which we actually are after last FindNext call
Private ml_FoundNodeInfoIndex As Long
'used for functon FindSQL, when searching for nodes which meet condition - they are childs (must not be direct) of this mo_FoundNodeParent
Private mo_FoundNodeParent As Node
'used for functon FindSQL, when searching for nodes which meet condition - they are in level ml_FoundNodeLevel
Private ml_FoundNodeLevel As Long
'number of levels treeview has/will load
Private ml_Levels As Long
'on which level we start loading on demand
Private ml_DemandLevel As Long
'SQL requests or cursor used for load all levels
Private mv_NodeRequests As Variant
'SQL request used for determine number of children for particular parent node in particular level
Private mv_CountRequests As Variant
'array of image indexes for icons in each level (normal state)
Private mv_Images As Variant
'array of image indexes for icons in each level (selected state)
Private mv_SelectedImages As Variant
'SQL request or cursor used to filter and load another component (grid)
Private mav_GridRequests As Variant
'SQL request or cursor used to filter and load another component (grid)
Private mav_GridCountRequests As Variant
'names of column which contain nod ID values in each level
Private ms_KeyFieldsNames() As String
'find request string for searching of nodes in database
Private mas_FindRequests As Variant
'allow or disallow use of popup menu
Private mb_UsePopup As Boolean
'store array of names for each level there is stored array of names for additional data values
Private mav_NodeDataNames() As Variant
'store level names for find frame
Private mv_LevelNames As Variant
'store size of node data in each level - has sense only by LoadTypeTree
Private mv_NodeDataSizes As Variant
'reference to ArmDB instance used for loading treeview
Private mo_Db As Object
'status if ArmDB is connected or not
Private mb_Connected As Boolean
'trace component to report programm flow and errors
Private mo_Trace As Object
Private mb_FirstFind As Boolean
'language code for user interface components in treeview control (menus,labels etc)
Private ms_LanguageCode As String
'component created the connection itself internally
Private mb_InternalConnection As Boolean
'set component to offline mode if true
Private mb_Offline As Boolean
'connect parameters
Private ms_Server As String
Private ms_Db As String
Private ms_User As String
Private ms_Pwd As String
Private ms_App As String
' flag if test duplicy when inserting new node
Private mb_removeNodeDuplicity As Boolean


'**************************************************************************************************************
'PUBLIC PROPERTIES
'**************************************************************************************************************

Public Property Set ArmDb(ByVal lo_Db As ARMSYSCOMLib.ArmDb)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ArmDb_Set")
#End If
  
  Set mo_Db = lo_Db
  mb_Connected = Not (lo_Db Is Nothing)

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ArmDb_Set")
#End If
End Property

'set and get reference to ArmDB component (should be used for DBF access, because only one instance can access
'DBF file.
Public Property Get ArmDb() As ARMSYSCOMLib.ArmDb
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ArmDb_Get")
#End If
  
  Set ArmDb = mo_Db

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ArmDb_Get")
#End If
End Property

Property Get IsConnected() As Boolean

On Error GoTo ErrorHandler
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:IsConnected_Get")
#End If
  
  IsConnected = False
  If Not (mo_Db Is Nothing) Then
    IsConnected = mo_Db.IsConnected
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:IsConnected_Get")
#End If
  Exit Property
ErrorHandler:
#If CompDebug Then
    Call mo_Trace.WriteTraceError("ArmTreeView:IsConnected_Get")
#End If
  IsConnected = mb_Connected
End Property

'main trace component
Public Property Set ArmTrace(ByVal lo_NewValue As Object)

  Set mo_Trace = lo_NewValue
End Property

Public Property Get ArmTrace() As Object
  Set ArmTrace = mo_Trace
End Property

'set component to offline or online mode
Public Property Let Offline(ab_Value As Boolean)
  mb_Offline = ab_Value
End Property

Public Property Get Offline() As Boolean
  Offline = mb_Offline
End Property

Public Property Get FoundNodeInfo() As NodeInfo
'    Debug.Assert (ml_FoundNodeInfoIndex > 1 And ml_FoundNodeInfoIndex <= mo_FoundNodeInfos.Count + 1)
    If ml_FoundNodeInfoIndex > 1 And ml_FoundNodeInfoIndex <= mo_FoundNodeInfos.Count + 1 Then
        Set FoundNodeInfo = mo_FoundNodeInfos(ml_FoundNodeInfoIndex - 1)
    End If
End Property

' used to navigate search
Public Property Let FoundNodeInfoIndex(ByVal al_ind As Long)
    Debug.Assert (al_ind >= 1 And al_ind <= mo_FoundNodeInfos.Count)
    If al_ind >= 1 And al_ind <= mo_FoundNodeInfos.Count Then
        ml_FoundNodeInfoIndex = al_ind
    End If
End Property

'connection parameters in case component shold create connection itself
Property Let ConnectString(as_Value As String)
Dim la_Params() As String

On Error GoTo ErrorHandler
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ConnectString_Let")
#End If
  
  If (as_Value <> "") Then
    la_Params = Split(as_Value, SEP, 5)
    ms_Server = la_Params(0)
    ms_Db = la_Params(1)
    ms_User = la_Params(2)
    ms_Pwd = la_Params(3)
    If UBound(la_Params) >= 4 Then
      ms_App = la_Params(4)
    Else
      ms_App = "ArmTreeView"
    End If
  Else
    ms_Server = ""
    ms_Db = ""
    ms_User = ""
    ms_Pwd = ""
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ConnectString_Let")
#End If
  Exit Property
ErrorHandler:
  #If CompDebugTV Then
    Call mo_Trace.WriteTraceError("ArmTreeView:ConnectString_Let", "as_Value=" & as_Value)
  #End If
End Property

'open conection and create own instance of ArmDB if it was not passed through property
Private Function OpenConnection(as_Server As String, as_Db As String, as_User As String, _
    as_Pwd As String, as_App As String) As Boolean
Dim lb_Result As Boolean

On Error GoTo ErrorHandler
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:OpenConnection")
#End If
 
  lb_Result = False
  If IsConnected Then
    lb_Result = False
  Else
    mb_InternalConnection = True
    If mo_Db Is Nothing Then
      Set mo_Db = New ARMSYSCOMLib.ArmDb
    End If

    If (as_Server <> "") And (as_Db <> "") And (as_User <> "") Then
        lb_Result = mo_Db.Connect(as_Server, as_Db, as_User, as_Pwd, as_App)
        If Not lb_Result Then
          #If CompDebug Then
            Call mo_Trace.WriteTraceSQLError(mo_Db, "ArmTreeView:OpenConnection", "as_Server=" & as_Server, _
            "as_Db=" & as_Db, "as_User=" & as_User)
          #End If
        End If
    End If
    mb_Connected = lb_Result
  End If
  OpenConnection = lb_Result
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:OpenConnection")
#End If
  Exit Function
ErrorHandler:
  OpenConnection = False
  #If CompDebugTV Then
    Call mo_Trace.WriteTraceError("ArmTreeView:OpenConnection", "as_Server=" & as_Server, _
    "as_Db=" & as_Db, "as_User=" & as_User)
  #End If
End Function

'close connection if connection was created with OpenConnection method and
Private Sub CloseConnection()

 On Error GoTo ErrorHandler
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:CloseConnection")
#End If
  
  If IsConnected Then
    Call mo_Db.Disconnect
    mb_InternalConnection = False
  End If
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:CloseConnection")
#End If
  Exit Sub
ErrorHandler:
  #If CompDebugTV Then
    Call mo_Trace.WriteTraceError("ArmTreeView:CloseConnection")
  #End If
End Sub

'property to set and get selected node
Public Property Set SelectedItem(lo_Node As Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SelectedItem_Set")
#End If
  
  Set TreeViewCtrl.SelectedItem = lo_Node
  If Not (lo_Node Is Nothing) Then lo_Node.EnsureVisible

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SelectedItem_Set")
#End If
End Property

Public Property Get SelectedItem() As Node

  On Error Resume Next
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SelectedItem_Get")
#End If

  Set SelectedItem = TreeViewCtrl.SelectedItem

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SelectedItem_Get")
#End If
End Property

'property to get image list reference - read only
Public Property Get ImageList() As Object
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ImageList_Get")
#End If
  
  Set ImageList = ImageListCtrl

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ImageList_Get")
#End If
End Property
'property to get and set root ID value for treeview
Public Property Get RootValue() As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:RootValue_Get")
#End If
  
  RootValue = mv_RootValue

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:RootValue_Get")
#End If
End Property

Public Property Let RootValue(ByVal vNewValue As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:RootValue_Let")
#End If
  
  mv_RootValue = vNewValue

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:RootValue_Let")
#End If
End Property
'get back number of nodes loaded into treeview
Public Property Get Count() As Long
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Count_Get")
#End If
  
  Count = TreeViewCtrl.Nodes.Count

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Count_Get")
#End If
End Property
'get reference to all nodes in treeview
Public Function Nodes(ll_NodeIndex As Long) As Node
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Nodes")
#End If
  
  Set Nodes = Nothing
  If (ll_NodeIndex > 0) And (ll_NodeIndex <= Count) Then Set Nodes = TreeViewCtrl.Nodes(ll_NodeIndex)

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Nodes")
#End If
End Function
'extract reference to nodeinfo for particular node
Public Property Get NodeInfo(lo_Node As Node) As NodeInfo
  
  If Not (lo_Node Is Nothing) Then
    Set NodeInfo = lo_Node.Tag
  Else
    Set NodeInfo = Nothing
  End If
End Property
'get and set number of level which will treeview load
Public Property Get Levels() As Long
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Levels_Get")
#End If
  Levels = ml_Levels
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Levels_Get")
#End If
End Property

Public Property Let Levels(ByVal ll_Levels As Long)
Dim ll_Index As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Levels_Let")
#End If
  ml_Levels = ll_Levels
  ReDim mv_NodeRequests(ll_Levels - 1)    'reserve array for requests which load levels (children)...
  ReDim mv_CountRequests(ll_Levels - 1)   'reserve array for requests which return children count
  ReDim mv_Images(ll_Levels - 1)          'reserve array for image indexes - each level one index
  ReDim mv_SelectedImages(ll_Levels - 1)  'reserve array for image indexes when node in selected state
  ReDim ms_KeyFieldsNames(ll_Levels - 1)       'reserve array for column names which contain ID values in each level
  ReDim mav_NodeDataNames(ll_Levels - 1)  'reserve array for names, under which will be additional data displayed in popup
  ReDim mv_NodeDataSizes(ll_Levels - 1)   'reserve array for number of fields a node occupoed in one record
  For ll_Index = 0 To ll_Levels - 1
    mv_NodeDataSizes(ll_Index) = 2
  Next
  ReDim mas_FindRequests(ll_Levels - 1)
  ReDim mav_GridRequests(ll_Levels - 1)
  ReDim mav_GridCountRequests(ll_Levels - 1)
  Call InitLevelNames
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Levels_Let")
#End If
End Property
'set array of node requests, for each level one request, except if LoadTree parameter is LoadTypeTree
Public Property Let NodeRequests(ByVal lv_Requests As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:NodeRequests_Let")
#End If

  Call SetVarArray(lv_Requests, mv_NodeRequests)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:NodeRequests_Let")
#End If
End Property
'set array of node children count requests, for each level one request (only for LoadTypeChildsDemand)
Public Property Let CountRequests(ByVal lv_Requests As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:CountRequests_Let")
#End If

  Call SetVarArray(lv_Requests, mv_CountRequests)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:CountRequests_Let")
#End If
End Property
'set array of image indexes, each level one element
Public Property Let Images(ByVal lv_Images As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Images_Let")
#End If

  Call SetVarArray(lv_Images, mv_Images)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Images_Let")
#End If
End Property
'set array of image indexes for selected state, each level one element
Public Property Let SelectedImages(ByVal lv_Images As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SelectedImages_Let")
#End If

  Call SetVarArray(lv_Images, mv_SelectedImages)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SelectedImages_Let")
#End If
End Property
'allow displaying node icons in treeview
Public Property Get UseImages() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:UseImages_Get")
#End If

  UseImages = Not (TreeViewCtrl.ImageList Is Nothing)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:UseImages_Get")
#End If
End Property

Public Property Let UseImages(ByVal lb_UseImages As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:UseImages_Let")
#End If
  If lb_UseImages Then
    Set TreeViewCtrl.ImageList = ImageListCtrl
  Else
    Set TreeViewCtrl.ImageList = Nothing
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:UseImages_Let")
#End If
End Property
'get and set level number from which we start loading nodes on demand (only for LoadTypeChildsDemand)
Public Property Get StartDemandLevel() As Long
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:StartDemandLevel_Get")
#End If

  StartDemandLevel = ml_DemandLevel
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:StartDemandLevel_Get")
#End If
End Property

Public Property Let StartDemandLevel(ByVal ll_Level As Long)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:StartDemandLevel_Let")
#End If
  ml_DemandLevel = ll_Level
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:StartDemandLevel_Let")
#End If
End Property
'allow display of checkboxes in treeview
Public Property Get Checkboxes() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Checkboxes_Get")
#End If
  Checkboxes = (TreeView_GetImageList(TreeViewCtrl.hwnd, TVSIL_STATE) <> 0)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Checkboxes_Get")
#End If
End Property

Public Property Let Checkboxes(ByVal lb_NewValue As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Checkboxes_Let")
#End If
 
  MenuCountChecked.Visible = lb_NewValue
  MenuCheckAll.Visible = lb_NewValue
  MenuUnCheckAll.Visible = lb_NewValue
  If lb_NewValue Then
    Call TreeView_SetImageList(TreeViewCtrl.hwnd, ImageListCtrl.hImageList, TVSIL_STATE)
  Else
    Call TreeView_SetImageList(TreeViewCtrl.hwnd, 0, TVSIL_STATE)
  End If
  RaiseEvent CheckboxesChange(lb_NewValue)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Checkboxes_Let")
#End If
End Property

'allow or disallow user to switch on checkboxes with popup menu
Public Property Let AllowCheckboxes(ByVal lb_Value As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:AllowCheckboxes_Let")
#End If

  MenuCheckBoxes.Visible = lb_Value
  MenuCheckAll.Visible = lb_Value
  MenuUnCheckAll.Visible = lb_Value
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:AllowCheckboxes_Let")
#End If
End Property

'allow or disallow user to use find function
Public Property Let AllowFind(ByVal lb_Value As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:AllowFind_Let")
#End If

  MenuFind.Visible = lb_Value
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:AllowFind_Let")
#End If
End Property

'set and get request for finding records (nodes) in database
Public Property Get FindRequests() As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindRequests_Get")
#End If
  FindRequests = mas_FindRequests
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindRequests_Get")
#End If
End Property
'set and get array of SQL request for searching nodes in database, each find request element for each level
Public Property Let FindRequests(ByVal sNewValue As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:AFindRequests_Let")
#End If
  Call SetVarArray(sNewValue, mas_FindRequests)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindRequests_Let")
#End If
End Property

Public Property Get SelectedNodeRequest(Optional ByVal lb_CountRequest As Boolean = False) As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SelectedNodeRequest_Get")
#End If
   SelectedNodeRequest = GetNodeGridRequest(SelectedItem, lb_CountRequest)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SelectedNodeRequest_Get")
#End If
End Property

'filter GridRequests cursor or GridRequests queries on selected checkboxes
Private Function AddFilterRequest(ByRef lva_Requests As Variant, ByVal lo_Node As Node, ByRef lb_SQLRequest As Boolean, _
  Optional ByVal lb_CountRequest As Boolean = False) As Long
  
  Dim ll_State As Long, ll_CheckedCount As Long, ll_TempCursor As Long
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:AddFilterRequest")
#End If
  ll_CheckedCount = 0
  If lo_Node Is Nothing Then
    Set lo_Node = GetFirstRoot
    While Not (lo_Node Is Nothing)
      ll_CheckedCount = ll_CheckedCount + AddFilterRequest(lva_Requests, lo_Node, lb_SQLRequest, lb_CountRequest)
      Set lo_Node = lo_Node.Next
    Wend
  Else
    ll_State = NodeInfo(lo_Node).ml_State
    If ll_State = TVI_STATE_GRAYED Then
      Set lo_Node = lo_Node.Child
      While Not (lo_Node Is Nothing)
        ll_CheckedCount = ll_CheckedCount + AddFilterRequest(lva_Requests, lo_Node, lb_SQLRequest, lb_CountRequest)
        Set lo_Node = lo_Node.Next
      Wend
    ElseIf ll_State = TVI_STATE_CHECKED Then
      If lb_SQLRequest Then
        ReDim Preserve lva_Requests(UBound(lva_Requests) + 1)
        lva_Requests(UBound(lva_Requests)) = GetNodeGridRequest(lo_Node, lb_CountRequest)
      Else
        ll_TempCursor = GetNodeGridRequest(lo_Node)
        If lva_Requests = 0 Then
          lva_Requests = ll_TempCursor
        ElseIf ll_TempCursor <> 0 Then  'make join or with our existing and new
          lva_Requests = mo_Db.JoinOr(ll_TempCursor, lva_Requests, True)
        End If
      End If
      ll_CheckedCount = 1
    End If
  End If
  AddFilterRequest = ll_CheckedCount
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:AddFilterRequest")
#End If
End Function

Public Property Get CheckedNodesRequests(Optional ByVal lb_CountRequest As Boolean = False) As Variant
Dim ll_Index As Long
Dim ll_CheckedCount As Long
Dim lo_Node As Node
Dim lva_Requests As Variant
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:CheckedNodesRequests_Get")
#End If
  CheckedNodesRequests = Empty
  If (Levels > 0) And Checkboxes Then
    ll_CheckedCount = 0
    If IsValidCursor(mav_GridRequests(0)) Then
      lva_Requests = 0
      ll_CheckedCount = AddFilterRequest(lva_Requests, Nothing, False, lb_CountRequest)
    Else
      ReDim lva_Requests(0)
      ll_CheckedCount = AddFilterRequest(lva_Requests, Nothing, True, lb_CountRequest)
    End If
    If ll_CheckedCount > 0 Then CheckedNodesRequests = lva_Requests
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:CheckedNodesRequests_Get")
#End If
End Property

'when user clicks with right mouse button on treeview a popup menu with additional functions is displayed
Public Property Get UsePopup() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:UsePopup_Get")
#End If
  UsePopup = mb_UsePopup
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:UsePopup_Get")
#End If
End Property

Public Property Let UsePopup(ByVal bNewValue As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:UsePopup_Let")
#End If
  mb_UsePopup = bNewValue
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:UsePopup_Let")
#End If
End Property
'get set user friendly names for additional data column in nodeinfo structure
Public Property Get NodeDataNames(ByVal ll_Level As Long) As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:NodeDataNames_Get")
#End If
  
  NodeDataNames = Empty
  If IsArray(mav_NodeDataNames) Then
    If ((ll_Level >= 0) And (UBound(mav_NodeDataNames) >= ll_Level)) Then
      NodeDataNames = mav_NodeDataNames(ll_Level)
    End If
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:NodeDataNames_Get")
#End If
End Property

Public Property Let NodeDataNames(ByVal ll_Level As Long, ByVal vNewValue As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:NodeDataNames_Let")
#End If
  
  ReDim Preserve mav_NodeDataNames(ll_Level)
  mav_NodeDataNames(ll_Level) = vNewValue
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:NodeDataNames_Let")
#End If
End Property

Public Property Get NodeDataSizes() As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:NodeDataSizes_Get")
#End If
  
  NodeDataSizes = mv_NodeDataSizes
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:NodeDataSizes_Get")
#End If
End Property

Public Property Let NodeDataSizes(ByVal vNewValue As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:NodeDataSizes_Let")
#End If
 Call SetVarArray(vNewValue, mv_NodeDataSizes)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:NodeDataSizes_Let")
#End If
End Property

Public Property Get LoadType() As TreeLoadType
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadType_Get")
#End If
  
  LoadType = mut_TreeLoadType
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadType_Get")
#End If
End Property

Public Property Let LoadType(ByVal vNewValue As TreeLoadType)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadType_Let")
#End If

  mut_TreeLoadType = vNewValue
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadType_Let")
#End If
End Property

'get and set grid request (cursor,SQL)- this helps to automatically load another component (grid) and it will automatically
'create filter condition according selection in TreeView for this request ro cursor
Public Property Get GridRequests() As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GridRequests_Get")
#End If
  
  GridRequests = mav_GridRequests
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GridRequests_Get")
#End If
End Property

Public Property Let GridRequests(ByVal lav_GridRequests As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GridRequests_Let")
#End If
  
  Call SetVarArray(lav_GridRequests, mav_GridRequests)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GridRequests_Let")
#End If
End Property
'get and set grid request (cursor,SQL)- this helps to automatically load another component (grid) and it will automatically
'create filter condition according selection in TreeView for this request ro cursor
Public Property Get GridCountRequests() As Variant
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GridCountRequests_Get")
#End If
  
  GridCountRequests = mav_GridCountRequests
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GridCountRequests_Get")
#End If
End Property

Public Property Let GridCountRequests(ByVal lav_GridCountRequests As Variant)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GridCountRequests_Let")
#End If
  
  Call SetVarArray(lav_GridCountRequests, mav_GridCountRequests)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GridCountRequests_Let")
#End If
End Property

' to set check duplicity when inserting automacally loaded nodes
Public Property Get RemoveNodeDuplicity() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:RemoveNodeDuplicity_Get ")
#End If
  
  RemoveNodeDuplicity = mb_removeNodeDuplicity

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:RemoveNodeDuplicity_Get ")
#End If
End Property

Public Property Let RemoveNodeDuplicity(ByVal lb_Value As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:RemoveNodeDuplicity_Let")
#End If
  
  mb_removeNodeDuplicity = lb_Value

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:RemoveNodeDuplicity_Let")
#End If
End Property


'**************************************************************************************************************
'PUBLIC METHODS
'**************************************************************************************************************
'manually add node into treeview. Parameters are:
'lo_ParentNode - reference to parent node under which we add child
'lv_Key - node ID value
'ls_Text - node caption (text)
'li_Image - index to icon for node in normal state
'lut_LoadType - load type for manually added node
'li_NodeSort - (currently sort nod used)
'lv_Data - array of additional data
Public Function AddNode(Optional ByVal lo_ParentNode As Node = Nothing, Optional ByVal lv_Key As Variant = "", _
                        Optional ByVal ls_Text As String = "", Optional ByVal li_Image As Integer = -1, _
                        Optional ByVal li_SelectedImage As Integer = -1, Optional ByVal lut_LoadType As TreeLoadType = LoadTypeNone, _
                        Optional ByVal li_NodeSort As Integer = -1, Optional ByVal lv_Data As Variant = "") As Node

Dim lo_NodeInfo As NodeInfo
Dim ll_Index As Long
Dim ll_Level As Long

  'create new nodeinfo object
  Set lo_NodeInfo = New NodeInfo
  lo_NodeInfo.mb_AutoLoaded = False
  lo_NodeInfo.LoadType = lut_LoadType
  'set its default data size
  If Not IsArray(lv_Data) Then
    lo_NodeInfo.SetNodeDataSize (BASE_NODE_DATA_SIZE)
  Else
    lo_NodeInfo.SetNodeDataSize (BASE_NODE_DATA_SIZE + UBound(lv_Data) + 1)
    For ll_Index = BASE_NODE_DATA_SIZE To BASE_NODE_DATA_SIZE + UBound(lv_Data)
      Call lo_NodeInfo.SetData(ll_Index, lv_Data(ll_Index - BASE_NODE_DATA_SIZE))
    Next
  End If
  Call lo_NodeInfo.SetData(0, lv_Key)
  If Not (lo_ParentNode Is Nothing) Then
    Call lo_NodeInfo.SetData(1, NodeInfo(lo_ParentNode).IDValue)
  End If
  'set information about caption, image, selected image, another data
  ll_Level = 0
  If Not (lo_ParentNode Is Nothing) Then ll_Level = lo_ParentNode.Tag.ml_Level + 1
  Call lo_NodeInfo.SetData(2, ls_Text)
  If (li_Image < 0) And (ll_Level < Levels) Then li_Image = mv_Images(ll_Level)
  lo_NodeInfo.DirectImages = True
  lo_NodeInfo.ImageValue = li_Image
  If (li_SelectedImage < 0) And (ll_Level < Levels) Then li_SelectedImage = mv_SelectedImages(ll_Level)
  lo_NodeInfo.SelectedImageValue = li_SelectedImage
  'add it the same way like automatic adding to treeview
  Set AddNode = AddNodeWithNodeInfo(lo_ParentNode, lo_NodeInfo)
  Call InitNodeHandles(lo_ParentNode)
End Function
'remove node from treeview + all of its children
Public Sub RemoveNode(lo_Node As Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:RemoveNode")
#End If
  
  Call Clear(lo_Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:RemoveNode")
#End If
End Sub
'clear all tree nodes and all nodeinfo data
'I don't use standard clear method of treeview because its very bad and slow implemented by Microsoft in VB
Public Sub Clear(Optional lo_Node As Node = Nothing)
  Dim ll_NodeHandle As Long
  
  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Clear")
#End If
  
  Call SetMousePointer(False)        'sandhour
  Call SetRedraw(TreeViewCtrl.hwnd, False)          'do not redrav when we delete node
  Call ClearNodeRef(lo_Node)
  If lo_Node Is Nothing Then
    Do                            'delete manually all root nodes
        ll_NodeHandle = TreeView_GetRoot(TreeViewCtrl.hwnd)
        If ll_NodeHandle <= 0 Then Exit Do
        Call TreeView_DeleteItem(TreeViewCtrl.hwnd, ll_NodeHandle)
    Loop
  Else
    TreeViewCtrl.Nodes.Remove (lo_Node.Index)
  End If
  Call SetRedraw(TreeViewCtrl.hwnd, True)
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Clear")
#End If
  Exit Sub
CleanUp:
  Call SetRedraw(TreeViewCtrl.hwnd, True)
  Call SetMousePointer(True)
End Sub

'check if particular node is parent node of node lo_Node. Must be not directly
Public Function IsParentNode(ByVal lo_Node As Node, ByVal lo_ParentNode As Node) As Boolean

  IsParentNode = False
  If Not (lo_Node Is Nothing) Then
    Do
      If lo_Node.Parent Is lo_ParentNode Then
        IsParentNode = True
        Exit Do
      End If
      Set lo_Node = lo_Node.Parent
    Loop Until lo_Node Is Nothing
  End If
End Function

Public Function GetChildNodesIDArray(ByVal lo_Node As Node) As Variant
Dim lv_Data As Variant
Dim lo_Child As Node
Dim ll_Index As Long

On Error GoTo ErrorHandler
  lv_Data = Empty
  If Not (lo_Node Is Nothing) Then
    Call TreeViewCtrl_Expand(lo_Node)
    If lo_Node.Children > 0 Then
      ReDim lv_Data(lo_Node.Children - 1)
      ll_Index = 0
      Set lo_Child = lo_Node.Child
      While Not (lo_Child Is Nothing)
        lv_Data(ll_Index) = NodeInfo(lo_Child).IDValue
        Set lo_Child = lo_Child.Next
        ll_Index = ll_Index + 1
      Wend
    End If
  End If
  GetChildNodesIDArray = lv_Data
  Exit Function
ErrorHandler:
  #If CompDebug Then
    Call mo_Trace.WriteTraceError("ArmTreeView:GetChildNodesIDArray")
  #End If
End Function
'this is our main load method for reading tree nodes from database.
'LoadTypeTree  - load whole treeview with one SQL request or one cursor
'LoadTypeLevels - load tree level by level - for each level one SQL statement
'LoadTypeChilds - for each level and each node one request will be executed
'LoadTypeChildsDemand - same as previous, but childs will be loaded on user demand and not in one go
'LoadTypeBranch - tree structure is saved in parent child relation in database and will be restored in one go
Public Function LoadTree(ByVal lut_LoadType As TreeLoadType) As Boolean
Dim ll_RecursionCounter As Long

  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadTree")
#End If
  
  LoadTree = False
  Call SetMousePointer(False)
  Call SetRedraw(TreeViewCtrl.hwnd, False)
  If (lut_LoadType = LoadTypeBranch) Or (lut_LoadType = LoadTypeChilds) Or _
     (lut_LoadType = LoadTypeLevel) Or (lut_LoadType = LoadTypeChildsDemand) Or _
     (lut_LoadType = LoadTypeTree) Then
    Call Clear                         'clear nodes if any
    ll_RecursionCounter = 0           'reset recursion
    mut_TreeLoadType = lut_LoadType   'save last loadtype
    LoadTree = LoadTreeNodes(lut_LoadType, ll_RecursionCounter) 'load demanded nodes
  End If
  Call SetRedraw(TreeViewCtrl.hwnd, True)
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadTree")
#End If
  Exit Function
CleanUp:
  Call SetRedraw(TreeViewCtrl.hwnd, True)
  Call SetMousePointer(True)
End Function
'refresh treeview, save state clear, new load and restore treeview state
Public Function Refresh() As Boolean
Dim lo_ExpNodeInfos As New Collection
Dim lo_ChkNodeInfos As New Collection
Dim lo_SelNodeInfo As NodeInfo
Dim lo_Node As Node
Dim lo_NodeInfo As NodeInfo
  
  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Refresh")
#End If
  Refresh = False
  Call SetMousePointer(False)
  Set lo_SelNodeInfo = CreateNodePath(SelectedItem)
  
  Call StoreTreeViewExpandedState(Nothing, lo_ExpNodeInfos)
  
  Call StoreTreeViewCheckedState(Nothing, lo_ChkNodeInfos)
  
  'this should remove circular reference between nodeinfo (mo_Node) and node (Tag)
  'is this really needed for VB to properly free memory by its "garbage colector" ???
  'I think is not needed because of node is not public creatable object, so garbage collector doesn't
  'count it in a "used" reference
  'For Each lo_NodeInfo In lo_ExpNodeInfos
  '  Set lo_NodeInfo.mo_Node = Nothing
  'Next
  'If Not (lo_SelNodeInfo Is Nothing) Then Set lo_SelNodeInfo.mo_Node = Nothing
  
  Refresh = LoadTree(mut_TreeLoadType)
  
  Call RestoreTreeViewExpandedState(lo_ExpNodeInfos)
  
  Call RestoreTreeViewCheckedState(lo_ChkNodeInfos)
  
  If Not (lo_SelNodeInfo Is Nothing) Then
    Set lo_Node = LocateNodeInDB(lo_SelNodeInfo)
  End If
  'raise event click after treeview refresh on selected node
  If Not (TreeViewCtrl.SelectedItem Is Nothing) Then
    RaiseEvent NodeClick(TreeViewCtrl.SelectedItem)
  End If
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Refresh")
#End If
  Exit Function
CleanUp:
  Call SetMousePointer(True)
End Function

Private Sub FindNodesInTree(lv_Value As Variant, ll_NodeDataIndex As Long, Optional ByVal lo_ParentNode As Node = Nothing, Optional ll_Level As Long = -1)
Dim lo_Node As Node
Dim lb_NodeFound As Boolean

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindNodesInTree")
#End If
  
  If (Not IsEmpty(lv_Value)) And (Not IsNull(lv_Value)) And (lv_Value <> "") Then
  
    If lo_ParentNode Is Nothing Then
      Set lo_Node = GetFirstRoot                    'Get root (first child of nothing)
    Else
      If (ll_Level >= 0) Then
        'check if we are not behind level boundary
        If (lo_ParentNode.Tag.ml_Level > ll_Level) Then GoTo OutOfLevel
        'did we found our level ?
        lb_NodeFound = (lo_ParentNode.Tag.ml_Level = ll_Level)
      Else
        lb_NodeFound = True
      End If
      
      If lb_NodeFound Then
        'check if we have to search string inside caption
        If ll_NodeDataIndex < 0 Then
          lb_NodeFound = (InStr(1, lo_ParentNode.Text, lv_Value, vbTextCompare) > 0)
        Else
          'we search additonal data as string
          lb_NodeFound = (StrComp(NodeInfo(lo_ParentNode).GetData(ll_NodeDataIndex), lv_Value, vbTextCompare) = 0)
        End If
        If lb_NodeFound Then
          Call mo_FoundNodeInfos.Add(NodeInfo(lo_ParentNode))
        End If
      End If
      Set lo_Node = lo_ParentNode.Child                   'get first child of lo_Node
    End If
    
    While Not (lo_Node Is Nothing)
      Call FindNodesInTree(lv_Value, ll_NodeDataIndex, lo_Node, ll_Level)
      Set lo_Node = lo_Node.Next
    Wend
  End If
OutOfLevel:
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindNodesInTree")
#End If
End Sub

Private Sub FindNodesInCursor(ByVal al_Cursor As Long, av_Value As Variant, ByVal al_NodeDataIndex As Long, Optional al_Level As Long = -1)
Dim lo_NodeInfo As NodeInfo
Dim ll_Index As Long
Dim ll_DataIndex As Long
Dim ll_Row As Long
Dim ll_FilterCursor As Long
Dim lv_DistFields As Variant

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindNodesInCursor")
#End If
  
  If (al_Cursor <> 0) And (Not IsEmpty(av_Value)) And (Not IsNull(av_Value)) And (av_Value <> "") Then
    'we can search in cursor only key and desciption
    'key has dataindex 0 and description has dataindex 1
    '(in node data is description index 2 so thats is why here conversion
    If al_NodeDataIndex = 2 Then
      al_NodeDataIndex = 1
    Else
      al_NodeDataIndex = 0
    End If
    For ll_Index = 0 To Levels - 1
      
      ReDim lv_DistFields(ll_Index * 2 + 1)
      For ll_DataIndex = 0 To UBound(lv_DistFields)
        lv_DistFields(ll_DataIndex) = ll_DataIndex
      Next
      
      If (ll_Index = al_Level) Or (al_Level = -1) Then
        ll_FilterCursor = mo_Db.Distinct(al_Cursor, lv_DistFields)
        ll_FilterCursor = mo_Db.Filter(ll_FilterCursor, ll_Index * 2 + al_NodeDataIndex, "begin", av_Value, 1, True)
        For ll_Row = 0 To mo_Db.RowCount(ll_FilterCursor) - 1
          Set lo_NodeInfo = New NodeInfo
          With lo_NodeInfo
            Call .SetNodeDataSize(ll_Index)
            For ll_DataIndex = 0 To ll_Index
              Call .SetData(ll_DataIndex, mo_Db.GetFieldsAt(ll_FilterCursor, ll_Row, 2 * ll_DataIndex))
            Next
          End With
          Call mo_FoundNodeInfos.Add(lo_NodeInfo)
          Set lo_NodeInfo = Nothing
        Next
        Call mo_Db.Close(ll_FilterCursor)
      End If
    Next
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindNodesInCursor")
#End If
  Exit Sub
ErrorHandler:
  Call mo_Db.Close(ll_FilterCursor)
End Sub

'find treeview nodes which meet criteria - have on index ll_NodeDataIndex value lv_Value, are a child of ParentNode or are in Level ll_Levle
Public Function Find(lv_Value As Variant, ll_NodeDataIndex As Long, Optional lo_ParentNode As Node = Nothing, Optional ll_Level As Long = -1) As Boolean
Dim lo_NodeInfo As NodeInfo
Dim lo_Node As Node
Dim lb_NodeFound As Boolean
  
  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Find")
#End If
  Find = False
  Call SetMousePointer(False)
  'set parameters to module global
  ml_FoundNodeInfoIndex = 1
  Set mo_FoundNodeParent = lo_ParentNode
  ml_FoundNodeLevel = -1
  Call ClearCollection(mo_FoundNodeInfos)
  If (lo_ParentNode Is Nothing) And (mut_TreeLoadType = LoadTypeChildsDemand) _
     And IsValidCursor(mv_NodeRequests(0)) And (ll_Level <> 0) Then
    Call FindNodesInCursor(mv_NodeRequests(0), lv_Value, ll_NodeDataIndex, ll_Level)
  Else
    Call FindNodesInTree(lv_Value, ll_NodeDataIndex, lo_ParentNode, ll_Level)
  End If
  Find = FindNext
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Find")
#End If
  Exit Function
CleanUp:
  Call SetMousePointer(True)
End Function
'the same as Find, except we don't look into treeview, but into database for nodes - it executes SQL to get array
'of ID numbers as a path to found Node
Public Function FindSQL(ByVal lv_Value As Variant, Optional ByVal ll_Level As Long = -1) As Boolean
Dim lo_NewNodeInfo As New NodeInfo
Dim ll_Index As Long
Dim ls_Request As String

  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindSQL")
#End If
  FindSQL = False
  Call SetMousePointer(False)
  Call ClearCollection(mo_FoundNodeInfos)
  
  ml_FoundNodeInfoIndex = 1
  Set mo_FoundNodeParent = Nothing
  ml_FoundNodeLevel = -1
  
  If (Not IsEmpty(lv_Value)) And (Not IsNull(lv_Value)) Then
    For ll_Index = 0 To Levels - 1
      If (ll_Level = -1) Or (ll_Level = ll_Index) Then
        ls_Request = mas_FindRequests(ll_Index)
        'raise event (programmer can change Request statement ... )
        RaiseEvent TreeNodesLoad(LoadTypeFindSQL, Nothing, 0, ls_Request, lo_NewNodeInfo)
        ls_Request = ReplacePlaceHolder(ls_Request, 0, lv_Value)
        If (ls_Request <> "") And Not (lo_NewNodeInfo Is Nothing) Then
          'now load all found data records
          Call LoadData(mo_FoundNodeInfos, ls_Request, lo_NewNodeInfo, LoadTypeFindSQL)
        End If
      End If
    Next
  End If
  FindSQL = FindNext
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindSQL")
#End If
  Exit Function
CleanUp:
  Call SetMousePointer(True)
End Function
'find next node - just take another nodeinfo from mo_FoundNodeInfos collection and select it in treeview
Public Function FindNext() As Boolean
Dim lo_NodeInfo As NodeInfo
Dim lo_Node As Node
Dim lb_Result As Boolean
  
  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindNext")
#End If
  
  Call SetMousePointer(False)
  FindNext = False
  If (mo_FoundNodeInfos.Count > 0) Then
    'loop until while node found or while we get last nodeinfo from collection of found records
    Do While (ml_FoundNodeInfoIndex <= mo_FoundNodeInfos.Count) And (ml_FoundNodeInfoIndex > 0)
      'take next node info
      Set lo_NodeInfo = mo_FoundNodeInfos(ml_FoundNodeInfoIndex)
      'locate it in database (take conditions which are stored in module variables)
      Set lo_Node = LocateNodeInDB(lo_NodeInfo)
      lb_Result = Not (lo_Node Is Nothing)
      'position on next nodeinfo element in collection
      ml_FoundNodeInfoIndex = ml_FoundNodeInfoIndex + 1
      If lb_Result Then
        RaiseEvent NodeClick(lo_Node)
        Exit Do
      End If
    Loop
    If Not lb_Result Then
      Call MsgBox(Translate(cstNotFound, ms_LanguageCode))
    End If
    FindNext = lb_Result
  End If
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindNext")
#End If
  Exit Function
CleanUp:
  Call SetMousePointer(True)
End Function

Private Sub ChangeStateAll(al_State As Long)
Dim lo_Node As Node

  For Each lo_Node In TreeViewCtrl.Nodes
    Call SetNodeState(lo_Node, al_State, False)
  Next
End Sub

'uncheck all nodes in treeview
Public Sub UnCheckAll()
  
  Call ChangeStateAll(TVI_STATE_UNCHECKED)
End Sub

'uncheck all nodes in treeview
Public Sub CheckAll()
 
  Call ChangeStateAll(TVI_STATE_CHECKED)
End Sub

'**************************************************************************************************************
'PRIVATE METHODS
'**************************************************************************************************************
'clear recursively all nodeinfo references for particular node
Private Sub ClearNodeRef(ByVal lo_Node As Node)
Dim lo_NodeInfo As NodeInfo

  If lo_Node Is Nothing Then
    For Each lo_Node In TreeViewCtrl.Nodes
      Set lo_NodeInfo = lo_Node.Tag
      Set lo_NodeInfo.mo_Node = Nothing
      Set lo_Node.Tag = Nothing
    Next
  Else
    Set lo_NodeInfo = lo_Node.Tag
    Set lo_NodeInfo.mo_Node = Nothing
    Set lo_Node.Tag = Nothing
    Set lo_Node = lo_Node.Child
    Do While Not (lo_Node Is Nothing)
      Call ClearNodeRef(lo_Node)
      Set lo_Node = lo_Node.Next
    Loop
  End If
End Sub
'get nodeinfo of path to node (all ID's on path are in this node info)
Private Function CreateNodePath(ByVal lo_Node As Node) As NodeInfo
Dim lo_NodeInfo As NodeInfo
Dim ll_Index As Long

  Set lo_NodeInfo = Nothing
  If Not (lo_Node Is Nothing) Then
    Set lo_NodeInfo = New NodeInfo
    Call lo_NodeInfo.SetNodeDataSize(lo_Node.Tag.ml_Level)
    For ll_Index = lo_NodeInfo.Length To 0 Step -1
      If Not lo_Node Is Nothing Then
        Call lo_NodeInfo.SetData(ll_Index, lo_Node.Tag.IDValue)
        Set lo_Node = lo_Node.Parent
      End If
    Next
  End If
  Set CreateNodePath = lo_NodeInfo
End Function
' check if particular node has children, which state is expanded
Private Function HasExpandedChildren(ByVal lo_ParentNode As Node) As Boolean
Dim lo_Node As Node
  
  HasExpandedChildren = False
  Set lo_Node = lo_ParentNode.Child
  While Not (lo_Node Is Nothing)
    If lo_Node.Expanded Then
      HasExpandedChildren = True
      Exit Function
    End If
    Set lo_Node = lo_Node.Next
  Wend
End Function

'store treeview state before refresh - expanded items
Private Sub StoreTreeViewExpandedState(ByVal lo_ParentNode As Node, ByVal lo_NodeInfos As Collection)
Dim lo_Node As Node

  If Not (lo_NodeInfos Is Nothing) Then
    If lo_ParentNode Is Nothing Then
      Set lo_Node = GetFirstRoot
      While Not (lo_Node Is Nothing)
        Call StoreTreeViewExpandedState(lo_Node, lo_NodeInfos)
        Set lo_Node = lo_Node.Next
      Wend
    Else
      If lo_ParentNode.Expanded Then
        If HasExpandedChildren(lo_ParentNode) Then
          Set lo_Node = lo_ParentNode.Child
          While Not (lo_Node Is Nothing)
            Call StoreTreeViewExpandedState(lo_Node, lo_NodeInfos)
            Set lo_Node = lo_Node.Next
          Wend
        Else
          Call lo_NodeInfos.Add(CreateNodePath(lo_ParentNode))
        End If
      End If
    End If
  End If
End Sub
'store treeview state before refresh - checked items
Private Sub StoreTreeViewCheckedState(ByVal lo_ParentNode As Node, ByVal lo_NodeInfos As Collection)
Dim lo_Node As Node

  If Not (lo_NodeInfos Is Nothing) Then
    If lo_ParentNode Is Nothing Then
      Set lo_Node = GetFirstRoot
      While Not (lo_Node Is Nothing)
        Call StoreTreeViewCheckedState(lo_Node, lo_NodeInfos)
        Set lo_Node = lo_Node.Next
      Wend
    Else
      If lo_ParentNode.Tag.ml_State = TVI_STATE_GRAYED Then
        Set lo_Node = lo_ParentNode.Child
        While Not (lo_Node Is Nothing)
          Call StoreTreeViewCheckedState(lo_Node, lo_NodeInfos)
          Set lo_Node = lo_Node.Next
        Wend
      ElseIf lo_ParentNode.Tag.ml_State = TVI_STATE_CHECKED Then
        Call lo_NodeInfos.Add(CreateNodePath(lo_ParentNode))
      End If
    End If
  End If
End Sub
'restore treeview state after refresh - expanded items
Private Sub RestoreTreeViewExpandedState(ByVal lo_NodeInfos As Collection)
Dim lo_NodeInfo As NodeInfo
Dim lo_Node As Node

  Call SetRedraw(TreeViewCtrl.hwnd, False)
  For Each lo_NodeInfo In lo_NodeInfos
    Set lo_Node = LocateNodeInDB(lo_NodeInfo, False, True)
    'Set lo_Node = LocateNodeInTree(lo_NodeInfo.IDValue, , lo_NodeInfo.ml_Level)
    'If Not (lo_Node Is Nothing) Then
    '  Call TreeViewCtrl_Expand(lo_Node)
    '  If Not lo_Node.Expanded Then lo_Node.Expanded = True
    'End If
  Next
  Call SetRedraw(TreeViewCtrl.hwnd, True)
End Sub
'restore treeview state after refresh - checked items
Private Sub RestoreTreeViewCheckedState(ByVal lo_NodeInfos As Collection)
Dim lo_NodeInfo As NodeInfo
Dim lo_Node As Node

  Call SetRedraw(TreeViewCtrl.hwnd, False)
  For Each lo_NodeInfo In lo_NodeInfos
    Set lo_Node = LocateNodeInDB(lo_NodeInfo, False, False)
    Call SetNodeState(lo_Node, TVI_STATE_CHECKED, True)
    Call SetParentState(lo_Node)
  Next
  Call SetRedraw(TreeViewCtrl.hwnd, True)
End Sub
'get first root node in treeview
Private Function GetFirstRoot() As Node
Dim lo_Node As Node
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GetFirstRoot")
#End If
  
  Set lo_Node = Nothing
  If Count > 0 Then
    Set lo_Node = TreeViewCtrl.Nodes(1)
    Set lo_Node = lo_Node.Root
    If Not (lo_Node Is Nothing) Then lo_Node = lo_Node.FirstSibling
  End If
  Set GetFirstRoot = lo_Node

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GetFirstRoot")
#End If
End Function

Private Function ReplaceLevelPlaceHolder(ByVal ls_Str As String, ByVal ll_Level As Long, ByVal lo_NodeInfo As NodeInfo, Optional lb_OverrideNum As Boolean = False) As String
Dim ll_Index As Long
Dim lv_Value As Variant

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ReplaceLevelPlaceHolder")
#End If
  If Not (lo_NodeInfo Is Nothing) Then
    If InStr(1, ls_Str, PHolder & ll_Level & LDelimiter) > 0 Then
      For ll_Index = 0 To lo_NodeInfo.Length
        lv_Value = lo_NodeInfo.GetData(ll_Index)
        If (VarType(lv_Value) <> vbString) Or lb_OverrideNum Then
          ls_Str = Replace(ls_Str, PHolder & ll_Level & LDelimiter & ll_Index & PHolder, lv_Value)
        Else
          ls_Str = Replace(ls_Str, PHolder & ll_Level & LDelimiter & ll_Index & PHolder, "'" & lv_Value & "'")
        End If
      Next
    End If
  End If
  ReplaceLevelPlaceHolder = ls_Str
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ReplaceLevelPlaceHolder")
#End If
End Function

'replace all parameters in ls_Request by data values from NodeInfo object
Private Function ReplaceParameters(ByVal ls_Request As String, ByVal lo_NodeInfo As NodeInfo) As String
Dim ll_Index As Integer

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ReplaceParameters")
#End If
  If Not (lo_NodeInfo Is Nothing) Then
    For ll_Index = 0 To lo_NodeInfo.Length
      ls_Request = ReplacePlaceHolder(ls_Request, ll_Index, lo_NodeInfo.GetData(ll_Index))
    Next
    For ll_Index = lo_NodeInfo.ml_Level To 0 Step -1
      ls_Request = ReplaceLevelPlaceHolder(ls_Request, ll_Index, lo_NodeInfo)
      If (lo_NodeInfo.mo_Node Is Nothing) Then Exit For
      If (lo_NodeInfo.mo_Node.Parent Is Nothing) Then Exit For
      Set lo_NodeInfo = NodeInfo(lo_NodeInfo.mo_Node.Parent)
    Next
  End If
  ReplaceParameters = ls_Request
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ReplaceParameters")
#End If
End Function
'get request for parent node which will load all its childs, or only child count
Private Function GetNodeRequest(ll_Level As Long, lo_ParentNodeInfo As NodeInfo, ll_LoadType As Long) As Variant
Dim lo_ParentNode As Node

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GetNodeRequest")
#End If
  If Not (lo_ParentNodeInfo Is Nothing) Then
    Set lo_ParentNode = lo_ParentNodeInfo.mo_Node
  End If
  
  If (Levels > 0) Then
    If ll_LoadType = LoadTypeChildsCount Then
      If (ll_Level >= 0) And (ll_Level < Levels - 1) Then
        If IsValidCursor(mv_NodeRequests(0)) Then
          GetNodeRequest = 0
        Else
          GetNodeRequest = ReplaceParameters(CStr(mv_CountRequests(ll_Level)), lo_ParentNodeInfo)
        End If
      End If
    ElseIf (ll_LoadType = LoadTypeChildsDemand) Or (ll_LoadType = LoadTypeChilds) Then
      If (ll_Level >= 0) And (ll_Level < Levels) Then
        If IsValidCursor(mv_NodeRequests(0)) Then
          GetNodeRequest = mv_NodeRequests(0) 'FilterLevelsCursor(mv_NodeRequests(0), ll_Level, lo_ParentNode)
        Else
          GetNodeRequest = ReplaceParameters(CStr(mv_NodeRequests(ll_Level)), lo_ParentNodeInfo)
        End If
      End If
    Else
      If (ll_Level >= 0) And (ll_Level < Levels) Then
        If IsValidCursor(mv_NodeRequests(0)) Then
          GetNodeRequest = mv_NodeRequests(ll_Level)
        Else
          GetNodeRequest = ReplaceParameters(CStr(mv_NodeRequests(ll_Level)), lo_ParentNodeInfo)
        End If
      End If
    End If
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GetNodeRequest")
#End If
End Function

Private Function FilterLevelsCursor(ByVal ll_Curs As Long, ByVal ll_Level As Long, ByVal lo_ParentNode As Node) As Long
Dim lv_CurFields As Variant
Dim ll_Index As Long, ll_NodeDataStart As Long, ll_TempCur As Long
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FilterLevelsCursor")
#End If
  FilterLevelsCursor = 0
  If (ll_Curs <> 0) Then
    FilterLevelsCursor = mo_Db.Copy(ll_Curs)
    If Not (lo_ParentNode Is Nothing) Then
      ReDim lv_CurFields(ll_Level - 1)
      Do
        lv_CurFields(NodeInfo(lo_ParentNode).ml_Level) = NodeInfo(lo_ParentNode).IDValue
        Set lo_ParentNode = lo_ParentNode.Parent
      Loop Until lo_ParentNode Is Nothing
      For ll_Index = 0 To UBound(lv_CurFields)
        ll_TempCur = FilterLevelsCursor
        FilterLevelsCursor = mo_Db.Filter(ll_TempCur, NodeDataStart(ll_Index), "=", lv_CurFields(ll_Index))
        Call mo_Db.Close(ll_TempCur)
      Next
    End If
    
    ReDim lv_CurFields(mv_NodeDataSizes(ll_Level) - 1)
    ll_NodeDataStart = NodeDataStart(ll_Level)
    For ll_Index = 0 To UBound(lv_CurFields)
      lv_CurFields(ll_Index) = ll_NodeDataStart + ll_Index
    Next
    ll_TempCur = FilterLevelsCursor
    mo_Db.Fields(ll_TempCur) = lv_CurFields
    FilterLevelsCursor = mo_Db.Distinct(ll_TempCur)
    Call mo_Db.Close(ll_TempCur)
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FilterLevelsCursor")
#End If
End Function
'the main method responsible for loading tree nodes into treeview step by step (or in one go)
'it always fire a event to allow programmer to change loading parameters on any node or on any level
'll_LoadType - one of LoadTypeTree,LoadTypeBranch,LoadTypeChilds,LoadTypeChildsDemand,LoadTypeChildsCount,LoadTypeLevel
'RecursionCounter - just to take care we don't crash VB when programmer uses event wrong
'lo_ParentNodeInfo - object nodeinfo for parent node (node for which we load childs, child count) Is Nothing when LoadTypeLevele,LoadTypeTree...
'll_ActualLevel - level in which we will load nodes (starting at 0)

Private Function LoadTreeNodes(ByVal ll_LoadType As TreeLoadType, ByVal RecursionCounter As Long, Optional ByVal lo_ParentNodeInfo As NodeInfo = Nothing, Optional ByVal ll_ActualLevel As Long = 0) As Boolean
Dim lv_Request As Variant
Dim ll_NodeIndex As Long
Dim lo_CollectionNodeInfo As Collection
Dim lo_Node As Node
Dim lo_NodeInfo As New NodeInfo
Dim lb_LevelTypeFound As Boolean
 
  On Error GoTo CleanUp
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadTreeNodes")
#End If
  
  Call SetMousePointer(False)
  'except everything will be OK
  LoadTreeNodes = True
  'we don't want stack overflow when some developer uses this event in wrong way
  If RecursionCounter >= 50 Then
    MsgBox "Maximum recursion level reached.Loading tree nodes stopped."
    LoadTreeNodes = False
    GoTo ExitLoadTreeNodes
  End If
  'break when this was the last node
  If ll_LoadType = LoadTypeNone Then GoTo ExitLoadTreeNodes
  'when loading root nodes (no parent) create temporary lo_ParentNodeInfo and initialize it
  If (lo_ParentNodeInfo Is Nothing) And (ll_ActualLevel = 0) Then
    Set lo_ParentNodeInfo = New NodeInfo
    lo_ParentNodeInfo.SetNodeDataSize (BASE_NODE_DATA_SIZE)
    lo_ParentNodeInfo.IDValue = RootValue
    lo_ParentNodeInfo.ml_Level = -1
    Set lo_ParentNodeInfo.mo_Node = Nothing
  End If
  'this is just to set parameters which were given through simplified interface (Laurent change)
  If (Levels > 0) And (ll_ActualLevel < Levels) Then
    If (ll_LoadType = LoadTypeTree) Or (ll_LoadType = LoadTypeBranch) Then
      lo_NodeInfo.LoadType = LoadTypeNone
    Else
      lo_NodeInfo.LoadType = ll_LoadType
    End If
    lv_Request = GetNodeRequest(ll_ActualLevel, lo_ParentNodeInfo, ll_LoadType)
    lo_NodeInfo.DirectImages = True
    lo_NodeInfo.ImageValue = mv_Images(ll_ActualLevel)
    lo_NodeInfo.SelectedImageValue = mv_SelectedImages(ll_ActualLevel)
    lo_NodeInfo.ml_Level = ll_ActualLevel
    Set lo_NodeInfo.mo_Node = lo_ParentNodeInfo.mo_Node
  End If
  'raise event to allow programmer change loading parameters in last moment - only for string request
  If VarType(lv_Request) = vbString Then
    RaiseEvent TreeNodesLoad(ll_LoadType, lo_ParentNodeInfo, ll_ActualLevel, lv_Request, lo_NodeInfo)
  End If
  'create collection for loaded NodeInfo objects
  Set lo_CollectionNodeInfo = New Collection
  
  'load nodeinfo object into collection
  Call LoadData(lo_CollectionNodeInfo, lv_Request, lo_NodeInfo, ll_LoadType)
  'If IsValidCursor(lv_Request) Then
  '  If (ll_LoadType = LoadTypeChilds) Or (ll_LoadType = LoadTypeChildsDemand) Then
  '    Call mo_Db.Close(lv_Request)
  '  End If
  'End If
  'now according loadtype add nodes to treeview and attach nodeinfo objects from collection
  Select Case ll_LoadType
  'this only update number of childs for particular node (withou loading all nodes) and show plus/minus sign
  Case LoadTypeChildsCount:
    If Not (lo_ParentNodeInfo Is Nothing) Then
      lo_ParentNodeInfo.mb_ChildsCountDetected = True
      If (lo_CollectionNodeInfo.Count > 0) Then
        Set lo_NodeInfo = lo_CollectionNodeInfo(1)
        If IsNumeric(lo_NodeInfo.GetData(0)) Then
          'update plus/minus sign for node
          Call ShowPlusMinusSignForSelectedNode(lo_NodeInfo.GetData(0) > 0)
          lo_ParentNodeInfo.ml_ChildCount = lo_NodeInfo.GetData(0)
        End If
      End If
    End If
  'add all nodes in one go - create whole treeview structure from nodeinfo collection
  Case LoadTypeTree:
    Call LoadModeTree(lo_CollectionNodeInfo, lo_ParentNodeInfo.mo_Node)
    RaiseEvent TreeNodesLoaded(ll_LoadType, lo_ParentNodeInfo, ll_ActualLevel, lv_Request)
  'add one branch - create this branch from nodeinfo collection, parent-child relationship must be used in db
  Case LoadTypeBranch:
    Call LoadModeBranch(lo_CollectionNodeInfo, lo_ParentNodeInfo.mo_Node)
    RaiseEvent TreeNodesLoaded(ll_LoadType, lo_ParentNodeInfo, ll_ActualLevel, lv_Request)
    For Each lo_NodeInfo In lo_CollectionNodeInfo    'for each of this nodes call recursivly loadtree and get request
      Set lo_Node = lo_NodeInfo.mo_Node
      If Not (lo_Node Is Nothing) Then
        ll_LoadType = lo_NodeInfo.LoadType
        If (ll_LoadType = LoadTypeChilds) And (Not lo_NodeInfo.mb_ChildrenLoaded) Then
          LoadTreeNodes = LoadTreeNodes(ll_LoadType, RecursionCounter + 1, NodeInfo(lo_Node), ll_ActualLevel + 1)
          If Not LoadTreeNodes Then Exit For
        End If
      End If
    Next
  'add one level in one go, child nodes are automatically attached to parent nodes in previous level
  'needed condition: node ID in parent level MUST be unique
  Case LoadTypeLevel:
    Call LoadModeLevel(lo_CollectionNodeInfo, ll_ActualLevel)
    RaiseEvent TreeNodesLoaded(ll_LoadType, lo_ParentNodeInfo, ll_ActualLevel, lv_Request)
    lb_LevelTypeFound = False
    For Each lo_NodeInfo In lo_CollectionNodeInfo
      Set lo_Node = lo_NodeInfo.mo_Node
      If Not (lo_Node Is Nothing) Then
        ll_LoadType = lo_NodeInfo.LoadType
        If (ll_LoadType = LoadTypeLevel) Then lb_LevelTypeFound = True
        If (ll_LoadType = LoadTypeChilds) Or (ll_LoadType = LoadTypeBranch) Then
          LoadTreeNodes = LoadTreeNodes(ll_LoadType, RecursionCounter + 1, NodeInfo(lo_Node), ll_ActualLevel + 1)
          If Not LoadTreeNodes Then Exit For
        End If
      End If
    Next
    If lb_LevelTypeFound And LoadTreeNodes Then LoadTreeNodes = LoadTreeNodes(LoadTypeLevel, RecursionCounter + 1, Nothing, ll_ActualLevel + 1)
  'add childs for particular parent (on demand or in one go) according data in nodeinfo collection
  Case LoadTypeChilds, LoadTypeChildsDemand:
    Call LoadModeChilds(lo_CollectionNodeInfo, lo_ParentNodeInfo.mo_Node)
    RaiseEvent TreeNodesLoaded(ll_LoadType, lo_ParentNodeInfo, ll_ActualLevel, lv_Request)
    For Each lo_NodeInfo In lo_CollectionNodeInfo
      Set lo_Node = lo_NodeInfo.mo_Node
      If Not (lo_Node Is Nothing) Then
        ll_LoadType = lo_NodeInfo.LoadType
        If (ll_LoadType = LoadTypeChilds) Or (ll_LoadType = LoadTypeBranch) Or _
           ((ll_LoadType = LoadTypeChildsDemand) And (StartDemandLevel - 1 > ll_ActualLevel)) Then
          LoadTreeNodes = LoadTreeNodes(ll_LoadType, RecursionCounter + 1, lo_NodeInfo, ll_ActualLevel + 1)
          If Not LoadTreeNodes Then Exit For
        End If
      End If
    Next
  End Select
ExitLoadTreeNodes:
  Call SetMousePointer(True)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadTreeNodes")
#End If
  Exit Function
CleanUp:
  Call SetMousePointer(True)
End Function
'display or hide plus/minus sign in front of treeview node
Private Sub ShowPlusMinusSignForSelectedNode(lb_Visible As Boolean)
Dim pitem As tvItem

  pitem.hItem = TreeView_GetSelection(TreeViewCtrl.hwnd)
  pitem.mask = TVIF_CHILDREN
  pitem.cChildren = Abs(lb_Visible)
  Call TreeView_SetItem(TreeViewCtrl.hwnd, pitem)
End Sub
'dynamically update popup menu - "more info" submenu and show there additional data information
'according property NodeDataNames - here are stored user friendly names for data columns
Private Sub UpdateMenuInfo(lo_Node As Node)
Dim ll_InfoCount As Long, ll_Index As Long, ll_Level As Long
Dim lo_NodeInfo As NodeInfo
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:UpdateMenuInfo")
#End If
  'first unload previous created dynamic submenu
  ll_InfoCount = 0
  For ll_Index = 1 To MenuData.UBound
    Unload MenuData(ll_Index)
  Next
    
  If Not (lo_Node Is Nothing) Then
    Set lo_NodeInfo = NodeInfo(lo_Node)
    ll_Level = GetNodeLevel(lo_Node)      'in which level we are ?
    If IsArray(mav_NodeDataNames) Then        'is array of NodeDataNames set ?
      If ((ll_Level >= 0) And (ll_Level <= UBound(mav_NodeDataNames))) Then
        If IsArray(mav_NodeDataNames(ll_Level)) Then 'is a element of DatNames an array ?
          For ll_Index = 0 To UBound(mav_NodeDataNames(ll_Level)) 'read all elements and dynamically update menu
            If mav_NodeDataNames(ll_Level)(ll_Index) <> "" Then
              If ll_InfoCount > 0 Then Load MenuData(ll_InfoCount)
              MenuData(ll_InfoCount).Caption = mav_NodeDataNames(ll_Level)(ll_Index) & " = " & lo_NodeInfo.GetData(ll_Index)
              ll_InfoCount = ll_InfoCount + 1
            End If
          Next
        End If
      End If
    End If
  End If
  'enable submenu if at least one element was added
  MenuInfo.Enabled = ll_InfoCount > 0
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:UpdateMenuInfo")
#End If
End Sub

Private Sub cmbLevel_Click()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:cmbLevel_Click")
#End If
  
  mb_FirstFind = True
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:cmbLevel_Click")
#End If
End Sub

Private Sub cmdCancel_Click()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:cmdCancel_Click")
#End If
  
  FindShow = False
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:cmdCancel_Click")
#End If
End Sub

Private Sub cmdFindNext_Click()
Dim lb_Found As Boolean
Dim lb_DoSearch As Boolean

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:cmdFindNext_Click")
#End If
  lb_DoSearch = True
  RaiseEvent FindNextClick(txtFind.Text, lb_DoSearch)
  If lb_DoSearch Then
      lb_Found = False
      If mb_FirstFind Then
        mb_FirstFind = False
        If (mut_TreeLoadType = LoadTypeChildsDemand) Then
          If IsValidCursor(mv_NodeRequests(0)) Then
            lb_Found = Find(txtFind.Text, 2, , cmbLevel.ItemData(cmbLevel.ListIndex))
          Else
            lb_Found = FindSQL(txtFind.Text, cmbLevel.ItemData(cmbLevel.ListIndex))
          End If
        Else
          lb_Found = Find(txtFind.Text, 2, , cmbLevel.ItemData(cmbLevel.ListIndex))
        End If
      Else
        lb_Found = FindNext
      End If
      RaiseEvent NodeFound(lb_Found)
    
      If Not lb_Found Then
        mb_FirstFind = True
      End If
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:cmdFindNext_Click")
#End If
End Sub

Private Sub MenuCheckAll_Click()

  Call ChangeStateAll(TVI_STATE_CHECKED)
End Sub

Private Sub MenuUnCheckAll_Click()

  Call ChangeStateAll(TVI_STATE_UNCHECKED)
End Sub

'show/hide checkboxes automatically when user click this menu item
Private Sub MenuCheckBoxes_Click()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:MenuCheckBoxes_Click")
#End If
  
  MenuCheckBoxes.Checked = Not MenuCheckBoxes.Checked
  Checkboxes = MenuCheckBoxes.Checked
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:MenuCheckBoxes_Click")
#End If
End Sub
'show common find dialog and allow search in treeview (or in SQL database)
Private Sub MenuFind_Click()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:MenuFind_Click")
#End If
  
  FindShow = Not FindShow
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:MenuFind_Click")
#End If
End Sub
'Raise event click
Private Sub TreeViewCtrl_Click()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_Click")
#End If
  
  RaiseEvent Click
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_Click")
#End If
End Sub
'raise event collapse
Private Sub TreeViewCtrl_Collapse(ByVal Node As MSComctlLib.Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_Collapse")
#End If
  
  RaiseEvent Collapse(Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_Collapse")
#End If
End Sub
'on doubleclick expand node if we are expanding one demand (otherwise this is automatic)
Private Sub TreeViewCtrl_DblClick()
Dim lo_NodeInfo As NodeInfo
Dim lo_Node As Node
Dim ll_RecursionCounter As Long
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_DblClick")
#End If
  
  RaiseEvent DblClick
  Set lo_Node = TreeViewCtrl.SelectedItem
  If Not (lo_Node Is Nothing) Then
    Set lo_NodeInfo = NodeInfo(lo_Node)
    If (lo_NodeInfo.LoadType = LoadTypeChildsDemand) And (Not lo_NodeInfo.mb_ChildrenLoaded) Then
      Call TreeViewCtrl_Expand(lo_Node)
      lo_Node.Expanded = True
    End If
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_DblClick")
#End If
End Sub
'raise expand event and if node is loaded on demand, load all children for it
Private Sub TreeViewCtrl_Expand(ByVal Node As MSComctlLib.Node)
Dim lo_NodeInfo As NodeInfo
Dim ll_RecursionCounter As Long
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_Expand")
#End If
  
  RaiseEvent Expand(Node)
  Set lo_NodeInfo = NodeInfo(Node)

  If Not lo_NodeInfo Is Nothing Then
    If (lo_NodeInfo.LoadType = LoadTypeChildsDemand) And (Not lo_NodeInfo.mb_ChildrenLoaded) Then
      Call LoadTreeNodes(LoadTypeChildsDemand, ll_RecursionCounter, lo_NodeInfo, GetNodeLevel(Node) + 1)
    End If
End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_Expand")
#End If
End Sub
'raise event keydown
Private Sub TreeViewCtrl_KeyDown(KeyCode As Integer, Shift As Integer)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_KeyDown")
#End If
  
  RaiseEvent KeyDown(KeyCode, Shift)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_KeyDown")
#End If
End Sub
'raise event keypress
Private Sub TreeViewCtrl_KeyPress(KeyAscii As Integer)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_KeyPress")
#End If
  
  RaiseEvent KeyPress(KeyAscii)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_KeyPress")
#End If
End Sub
'raise event keyup
Private Sub TreeViewCtrl_KeyUp(KeyCode As Integer, Shift As Integer)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_KeyUp")
#End If
  
  RaiseEvent KeyUp(KeyCode, Shift)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_KeyUp")
#End If
End Sub
'mouse down - if user pressed right button, show popup menu
'automatically count number of checked nodes, number of children and number of all nodes in treeview
Private Sub TreeViewCtrl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  Dim tvhti As TVHITTESTINFO
  Dim lo_Node As Node
  Dim ll_Count As Long, ll_Index As Long, ll_State As Long
  Dim lb_Allow As Boolean
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_MouseDown")
#End If
  
  If ((Button And vbRightButton) And mb_UsePopup) Then
    If SelectedItem Is Nothing Then
      ll_Count = 0
    Else
      ll_Count = SelectedItem.Children
      If ll_Count = 0 Then
        ll_Count = NodeInfo(SelectedItem).ml_ChildCount
      End If
    End If
    MenuCountChildren.Caption = Translate(cstCountChildren, ms_LanguageCode) & " " & ll_Count
    MenuCountAll.Caption = Translate(cstCountAll, ms_LanguageCode) & " " & Count
    'count number of checked nodes
    If Checkboxes Then
      ll_Count = 0
      For ll_Index = 1 To Count
        If NodeInfo(Nodes(ll_Index)).ml_State = TVI_STATE_CHECKED Then ll_Count = ll_Count + 1
      Next
      MenuCountChecked.Caption = Translate(cstCountChecked, ms_LanguageCode) & " " & ll_Count
    End If
    Call UpdateMenuInfo(SelectedItem)
    MenuCheckBoxes.Checked = Checkboxes
    PopupMenu MenuPopup
  End If
  'test if user click an items button (plus sign), if yes expand (this is not done automatically
  'because node has only plus sign, no children are loaded yet !!!
  If (Button And vbLeftButton) Then
    
    ' If an item's buttom is clicked...
    tvhti.pt.X = X / Screen.TwipsPerPixelX
    tvhti.pt.Y = Y / Screen.TwipsPerPixelY
        
    If TreeView_HitTest(TreeViewCtrl.hwnd, tvhti) Then    ' also rtns hItem
      If (tvhti.flags And TVHT_ONITEMBUTTON) Then
        
        ' Get the Node's object reference. The TreeView's HitTest method
        ' does not return a Node reference if the specified point is over a
        ' Node's button. Since TreeView buttons are 16x16 icons, add 16
        ' twips to the x param to shift the point to over the Node's icon.
        Set lo_Node = TreeViewCtrl.HitTest(X + (16 * Screen.TwipsPerPixelX), Y)
        If Not (lo_Node Is Nothing) Then
          If Not lo_Node.Expanded Then
            Call TreeViewCtrl_Expand(lo_Node)
          End If  ' Not lo_Node.Expanded
        End If   ' (lo_Node Is Nothing)
      ElseIf (tvhti.flags And TVHT_ONITEMSTATEICON) Then
        Set lo_Node = TreeViewCtrl.HitTest(X, Y)
'        Set lo_Node = TreeViewCtrl.HitTest(x + (16 * Screen.TwipsPerPixelX), y)
        If Not (lo_Node Is Nothing) Then
          ll_State = NodeInfo(lo_Node).ml_State
          lb_Allow = True
          RaiseEvent BeforeNodeCheck(lo_Node, lb_Allow)
          If lb_Allow Then
          If (ll_State = TVI_STATE_CHECKED) Or (ll_State = TVI_STATE_GRAYED) Then
            Call SetNodeState(lo_Node, TVI_STATE_UNCHECKED, True)
          Else
            Call SetNodeState(lo_Node, TVI_STATE_CHECKED, True)
          End If
            RaiseEvent NodeCheck(lo_Node)
          Call SetParentState(lo_Node)
          End If
        End If
      End If   ' (tvhti.flags And TVHT_ONITEMBUTTON)
    End If   ' TreeView_HitTest
  End If   ' Button = vbLeftButton

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_MouseDown")
#End If
End Sub
'raise event NodeClick, if LoadTypeChildsDemand, count number of children nodes from database (if it was not already done)
Private Sub TreeViewCtrl_NodeClick(ByVal Node As MSComctlLib.Node)
Dim lo_NodeInfo As NodeInfo
Dim ll_RecursionCounter As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_NodeClick")
#End If
  
  RaiseEvent NodeClick(Node)
  Set lo_NodeInfo = NodeInfo(Node)

  If (lo_NodeInfo.LoadType = LoadTypeChildsDemand) And (Not lo_NodeInfo.mb_ChildsCountDetected) And (Not lo_NodeInfo.mb_ChildrenLoaded) Then
    Call LoadTreeNodes(LoadTypeChildsCount, ll_RecursionCounter, NodeInfo(Node), GetNodeLevel(Node))
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_NodeClick")
#End If
End Sub
'raise event NodeCheck
Private Sub TreeViewCtrl_NodeCheck(ByVal Node As MSComctlLib.Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:TreeViewCtrl_NodeCheck")
#End If
  
  RaiseEvent NodeCheck(Node)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:TreeViewCtrl_NodeCheck")
#End If
End Sub

Private Sub txtFind_Change()
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:txtFind_Change")
#End If
  
  mb_FirstFind = True
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:txtFind_Change")
#End If
End Sub

Private Sub txtFind_KeyDown(KeyCode As Integer, Shift As Integer)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:txtFind_KeyDown")
#End If
  
  If KeyCode = vbKeyReturn Then cmdFindNext.Value = True

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:txtFind_KeyDown")
#End If
End Sub

'Initialize control - set some properties and
Public Sub Load_A_Com()
Dim ll_Index As Long
  
  mb_Connected = False
  Set mo_FoundNodeInfos = New Collection
  ml_Levels = 0
  mb_UsePopup = True
  mb_removeNodeDuplicity = False

On Error GoTo NoMoreImages
  ll_Index = 101
  Do
    Call ImageListCtrl.ListImages.Add(, , LoadResPicture(ll_Index, vbResBitmap))
    ll_Index = ll_Index + 1
  Loop
NoMoreImages:

  With TreeViewCtrl
    .HideSelection = False
    .ImageList = ImageListCtrl
    .Indentation = 19 * Screen.TwipsPerPixelX
    .LabelEdit = tvwManual
    .LineStyle = tvwRootLines
    Set .SelectedItem = Nothing
  End With
  If ms_LanguageCode = "" Then ms_LanguageCode = "E"
  mb_InternalConnection = False
End Sub

'Unload COM armstrong function
Public Sub Unload_A_Com()

  Call Clear
  If mb_InternalConnection Then
    Call CloseConnection
  End If
  Set mo_Db = Nothing
  Set mo_FoundNodeInfos = Nothing
End Sub


'resize treeview to fit user control and raise event resize
Private Sub UserControl_Resize()

  frmFind.top = 0
  frmFind.left = 0
  frmFind.Width = Width
  TreeViewCtrl.left = 0
  TreeViewCtrl.Width = Width
  cmdCancel.left = frmFind.Width - cmdCancel.Width - cmdFindNext.left
  If frmFind.Visible Then
    TreeViewCtrl.top = frmFind.Height
    TreeViewCtrl.Height = Height - frmFind.Height
    cmbLevel.Width = frmFind.Width - 2 * cmbLevel.left
    txtFind.Width = frmFind.Width - 2 * txtFind.left
  Else
    TreeViewCtrl.top = 0
    TreeViewCtrl.Height = Height
  End If
  RaiseEvent Resize
End Sub

'load one particular node from database into treeview, this is used just in special case when
'using FindSQL method and it find a node which is not yet loaded into treeview, because it is
'in LoadChildsDemand mode
Private Function LoadNode(ByVal lo_NodeInfo As NodeInfo, ll_RecursionCounter) As Node
Dim lo_Node As Node
Dim lo_ParentNode As Node
Dim lo_ParentNodeInfo As New NodeInfo
Dim lo_ParentNodeCollection As Collection
Dim ls_Request As String

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadNode")
#End If
  Set LoadNode = Nothing
  
  If ll_RecursionCounter >= 50 Then 'we don't want stack overflow when developer uses event wrong
    MsgBox "Maximum recursion level reached.Loading Parent tree nodes stopped."
    Exit Function
  End If
  'try to find it in tree
  Set lo_Node = LocateNodeInTree(lo_NodeInfo.IDValue, mo_FoundNodeParent, ml_FoundNodeLevel)
  'if not found then try to load it from database according its ID
  If lo_Node Is Nothing Then
    'if we don't have right ID or ParentID is RootValue place this node to the root
    If IsEmpty(lo_NodeInfo.ParentIDValue) Or IsNull(lo_NodeInfo.ParentIDValue) Or _
       (lo_NodeInfo.ParentIDValue = "") Or (lo_NodeInfo.ParentIDValue = RootValue) Then
      Set lo_ParentNode = Nothing
    Else
      'else try to load it from database , first raise event to get ls_Request or ParentNode
      RaiseEvent TreeNodesLoad(LoadTypeParent, lo_NodeInfo, 0, ls_Request, lo_ParentNodeInfo)
      Set lo_ParentNode = lo_ParentNodeInfo.mo_Node
      If lo_ParentNode Is Nothing Then
        Set lo_ParentNodeCollection = New Collection
        Call LoadData(lo_ParentNodeCollection, ls_Request, lo_ParentNodeInfo, LoadTypeParent)
        If lo_ParentNodeCollection.Count <> 1 Then
          Set lo_ParentNode = Nothing
        Else
          'try to recursive load its parent
          Set lo_ParentNode = LoadNode(lo_ParentNodeCollection(1), ll_RecursionCounter + 1)
          If Not (lo_ParentNode Is Nothing) Then
            Call TreeViewCtrl_Expand(lo_ParentNode)
            Set lo_Node = LocateNodeInTree(lo_NodeInfo.IDValue, mo_FoundNodeParent, ml_FoundNodeLevel)
          End If
        End If
      End If
    End If
  End If
  Set LoadNode = lo_Node
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadNode")
#End If
End Function
'fill name of key ID field for particular level into internal array ms_KeyFieldsNames (used by GridRequests condition)
Private Sub SetKeyFields(ByVal ll_Curs As Long, ByVal ll_Level As Long, ByVal ll_LoadType As TreeLoadType)
Dim ll_Index As Long
Dim ll_DataStart As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SetKeyFields")
#End If

  If (ll_Level >= 0) And (ll_Level < Levels) Then
    If ll_LoadType = LoadTypeTree Then
        For ll_Index = 0 To Levels - 1
          ll_DataStart = NodeDataStart(ll_Index)
          If ll_DataStart < mo_Db.FieldCount(ll_Curs) Then
            ms_KeyFieldsNames(ll_Index) = mo_Db.GetFieldName(ll_Curs, ll_DataStart)
          End If
        Next
    ElseIf (ll_LoadType = LoadTypeChilds) Or (ll_LoadType = LoadTypeChildsDemand) _
        Or (ll_LoadType = LoadTypeLevel) Then
      ms_KeyFieldsNames(ll_Level) = mo_Db.GetFieldName(ll_Curs, 0)
    End If
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SetKeyFields")
#End If
End Sub
'load data from cursor into collection of NodeInfo objects
Private Sub LoadDataCursor(lo_NodeInfos As Collection, ByVal ll_Curs As Long, lo_NodeInfo As NodeInfo, ByVal ll_LoadType As TreeLoadType)
Dim ll_ColCount As Integer, ll_ColIndex As Long, ll_Row As Long
Dim lo_NewNodeInfo As NodeInfo
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadDataCursor")
#End If
  If ll_Curs And (Not (lo_NodeInfos Is Nothing)) And (Not (mo_Db Is Nothing)) Then
    Call SetKeyFields(ll_Curs, lo_NodeInfo.ml_Level, ll_LoadType)
    ll_ColCount = mo_Db.FieldCount(ll_Curs)
    For ll_Row = 0 To mo_Db.RowCount(ll_Curs) - 1
      Set lo_NewNodeInfo = New NodeInfo
      With lo_NewNodeInfo
        Call .SetNodeDataSize(ll_ColCount - 1)
        For ll_ColIndex = 0 To ll_ColCount - 1
          Call .SetData(ll_ColIndex, mo_Db.GetFieldsAt(ll_Curs, ll_Row, ll_ColIndex))
        Next
       Call .CopyDataDef(lo_NodeInfo)
       .mb_AutoLoaded = True
      End With
      lo_NodeInfos.Add Item:=lo_NewNodeInfo
      Set lo_NewNodeInfo = Nothing
    Next
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadDataCursor")
#End If
End Sub

Private Sub GetParentKeyValue(ao_Node As Node, ByRef av_ParentKey As Variant, ByRef av_ParentValue As Variant)
Dim lo_Node As Node
  If ao_Node Is Nothing Then
    av_ParentKey = Empty
    av_ParentValue = Empty
  Else
    ReDim av_ParentKey(ao_Node.Tag.ml_Level)
    ReDim av_ParentValue(ao_Node.Tag.ml_Level)
    Set lo_Node = ao_Node
    Do While Not (lo_Node Is Nothing)
      av_ParentKey(lo_Node.Tag.ml_Level) = ms_KeyFieldsNames(lo_Node.Tag.ml_Level)
      av_ParentValue(lo_Node.Tag.ml_Level) = lo_Node.Tag.IDValue
      Set lo_Node = lo_Node.Parent
    Loop
  End If
End Sub
'load data from cursor into collection of NodeInfo objects
Private Sub LoadDataCursorChild(lo_NodeInfos As Collection, ByVal ll_Curs As Long, lo_NodeInfo As NodeInfo, ByVal ll_LoadType As TreeLoadType)
Dim ll_ColCount As Integer, ll_ColIndex As Long, ll_Row As Long
Dim lo_NewNodeInfo As NodeInfo
Dim ll_DistCursor As Long
Dim lv_DistFields As Variant, lv_SortFields As Variant
Dim lv_ParentKey As Variant
Dim lv_ParentValue As Variant
  
On Error GoTo ErrorHandler
  If ll_Curs And (Not (lo_NodeInfos Is Nothing)) And (Not (mo_Db Is Nothing)) Then
    Call SetKeyFields(ll_Curs, lo_NodeInfo.ml_Level, LoadTypeTree)
    ReDim lv_DistFields(lo_NodeInfo.ml_Level * 2 + 1)
    For ll_ColIndex = 0 To UBound(lv_DistFields)
      lv_DistFields(ll_ColIndex) = ll_ColIndex
    Next
    ReDim lv_SortFields(lo_NodeInfo.ml_Level)
    For ll_ColIndex = 0 To UBound(lv_SortFields)
      lv_SortFields(ll_ColIndex) = ll_ColIndex * 2 + 1
    Next
    'create distinct until level we are going to display
    ll_DistCursor = mo_Db.Distinct(ll_Curs, lv_DistFields)
    'sort according descriptions
    mo_Db.SortFields(ll_DistCursor) = lv_SortFields
    
    ll_ColCount = mo_Db.FieldCount(ll_DistCursor)
    Call GetParentKeyValue(lo_NodeInfo.mo_Node, lv_ParentKey, lv_ParentValue)
    ll_Row = 0
    Do
      If IsArray(lv_ParentKey) Then
        ll_Row = mo_Db.Find(ll_DistCursor, lv_ParentKey, lv_ParentValue, ll_Row)
        If ll_Row < 0 Then Exit Do
      Else
        If ll_Row >= mo_Db.RowCount(ll_DistCursor) Then Exit Do
      End If
      
      Set lo_NewNodeInfo = New NodeInfo
      With lo_NewNodeInfo
        Call .SetNodeDataSize(2)
        Call .CopyDataDef(lo_NodeInfo)
        .IDValue = mo_Db.GetFieldsAt(ll_DistCursor, ll_Row, ll_ColCount - 2)
        .TextValue = mo_Db.GetFieldsAt(ll_DistCursor, ll_Row, ll_ColCount - 1)
      End With
      Call lo_NodeInfos.Add(lo_NewNodeInfo)
      Set lo_NewNodeInfo = Nothing
      ll_Row = ll_Row + 1
    Loop
    Call mo_Db.Close(ll_DistCursor)
  End If
  Exit Sub
ErrorHandler:
  Call mo_Db.Close(ll_DistCursor)
End Sub

'load data into collection, parameter lv_Request can by SQL request or ArmDB cursor
Private Sub LoadData(lo_NodeInfos As Collection, ByVal lv_Request As Variant, lo_NodeInfo As NodeInfo, ByVal ll_LoadType As TreeLoadType)
Dim ll_Curs As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadData")
#End If
  If (Not (mo_Db Is Nothing)) Then
    'try to open connection if component is not connected
    If (Not mb_Offline) And (Not IsConnected) Then
      If Not OpenConnection(ms_Server, ms_Db, ms_User, ms_Pwd, ms_App) Then
        Call MsgBox("Open connection failed.")
      End If
    End If

    If IsValidCursor(lv_Request) Then
      If (ll_LoadType = LoadTypeChilds) Or (ll_LoadType = LoadTypeChildsDemand) Then
        Call LoadDataCursorChild(lo_NodeInfos, lv_Request, lo_NodeInfo, ll_LoadType)
      Else
        Call LoadDataCursor(lo_NodeInfos, lv_Request, lo_NodeInfo, ll_LoadType)
      End If
    ElseIf (lv_Request <> "") And IsConnected Then
      ll_Curs = mo_Db.OpenSQL(lv_Request, -1)
      If ll_Curs Then
        Call LoadDataCursor(lo_NodeInfos, ll_Curs, lo_NodeInfo, ll_LoadType)
        Call mo_Db.Close(ll_Curs)
      Else
#If CompDebug Then
        Call mo_Trace.WriteTraceSQLError(mo_Db, "ArmTreeView:LoadData", "lv_Request=" & lv_Request)
#End If
      End If
    End If
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadData")
#End If
End Sub

'get level in which is our node lo_Node
Private Function GetNodeLevel(lo_Node As Node) As Long
Dim lo_NodeInfo As NodeInfo

  GetNodeLevel = -1
  Set lo_NodeInfo = NodeInfo(lo_Node)
  If Not (lo_NodeInfo Is Nothing) Then GetNodeLevel = lo_NodeInfo.ml_Level
End Function
'this is one and only method which add node to treeview.It takes all data from nodeinfo object and
'attach this nodeinfo object to added node
Private Function AddNodeWithNodeInfo(lo_ParentNode As Node, lo_NodeInfo As NodeInfo) As Node
Dim lo_Node As Node

  If Not (lo_NodeInfo Is Nothing) Then
    ' JUNA 16.2.2006 test if node beeing inserted was not manally added before
    ' autoloaded nodes will not be added when manuly added already exists
    If lo_NodeInfo.mb_AutoLoaded And RemoveNodeDuplicity Then
        If lo_ParentNode Is Nothing Then
            If TreeViewCtrl.Nodes.Count <> 0 Then
                Set lo_Node = TreeViewCtrl.Nodes.Item(1).Root.FirstSibling
            End If
        Else
            Set lo_Node = lo_ParentNode.Child
        End If
        
        While Not lo_Node Is Nothing
            If Not lo_Node.Tag.mb_AutoLoaded Then
                ' check ID
                If lo_NodeInfo.IDValue = lo_Node.Tag.IDValue Then
                    ' this node already exists
                    Set AddNodeWithNodeInfo = lo_Node
                    Exit Function
                End If
            End If
            Set lo_Node = lo_Node.Next
        Wend
    End If
    ' JUNA 16.2.2006 END
    
    If lo_ParentNode Is Nothing Then
      Set lo_Node = TreeViewCtrl.Nodes.Add(, , , lo_NodeInfo.TextValue)
      lo_NodeInfo.ml_Level = 0
    Else
      Set lo_Node = TreeViewCtrl.Nodes.Add(lo_ParentNode.Index, tvwChild, , lo_NodeInfo.TextValue)
      NodeInfo(lo_ParentNode).mb_ChildrenLoaded = True
      lo_NodeInfo.ml_Level = GetNodeLevel(lo_ParentNode) + 1
    End If
    If UseImages Then
      If IsNumeric(lo_NodeInfo.ImageValue) Then
        If (lo_NodeInfo.ImageValue > 0) And (lo_NodeInfo.ImageValue <= ImageListCtrl.ListImages.Count) Then lo_Node.Image = lo_NodeInfo.ImageValue
      End If
      If IsNumeric(lo_NodeInfo.SelectedImageValue) Then
        If (lo_NodeInfo.SelectedImageValue > 0) And (lo_NodeInfo.SelectedImageValue <= ImageListCtrl.ListImages.Count) Then lo_Node.SelectedImage = lo_NodeInfo.SelectedImageValue
      End If
    End If
  
    Set lo_NodeInfo.mo_Node = lo_Node
    Set lo_Node.Tag = lo_NodeInfo
    Set AddNodeWithNodeInfo = lo_Node
    RaiseEvent TreeNodeAdded(lo_Node)
  End If
End Function

Private Function NodeDataStart(ll_Level As Long) As Long
Dim ll_Index As Long
    
    NodeDataStart = 0
    For ll_Index = 0 To ll_Level - 1
      NodeDataStart = NodeDataStart + mv_NodeDataSizes(ll_Index)
    Next
End Function

'add nodes from nodeinfo collection into treeview, collection was loaded in mode LoadTypeTree,
'in which we load one horizontal line of nodes in one record in format ID,caption,ID,caption.....
Private Function LoadModeTree(ByVal lo_NodeInfos As Collection, ByVal lo_ParentNode As Node) As Boolean
Dim lo_NodeInfo As NodeInfo, lo_NewNodeInfo As NodeInfo
Dim lo_Node As Node
Dim ll_Level As Long, ll_StartDataIndex As Long, ll_Index As Long
Dim lav_LastIDs As Variant
Dim lao_LastNodes() As Node
Dim lb_AddAllChilds As Boolean

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadModeTree")
#End If
  If Levels > 0 Then
    ReDim lav_LastIDs(Levels - 1)
    ReDim lao_LastNodes(Levels)
    Set lao_LastNodes(0) = lo_ParentNode
    For ll_Index = 0 To UBound(lav_LastIDs)
      lav_LastIDs(ll_Index) = Null
    Next
    
    For Each lo_NodeInfo In lo_NodeInfos
      'in one lo_NodeInfo we have nodes from all levels
      lb_AddAllChilds = False
      For ll_Level = 0 To Levels - 1
                
        ll_StartDataIndex = NodeDataStart(ll_Level)
        
        If lb_AddAllChilds Or IsNull(lav_LastIDs(ll_Level)) Or (lav_LastIDs(ll_Level) <> lo_NodeInfo.GetData(ll_StartDataIndex)) Then
          lb_AddAllChilds = True
          
          If mv_NodeDataSizes(ll_Level) > 0 Then
          Set lo_NewNodeInfo = New NodeInfo
          Call lo_NewNodeInfo.SetNodeDataSize(mv_NodeDataSizes(ll_Level) - 1)
          
          For ll_Index = 0 To mv_NodeDataSizes(ll_Level) - 1
            Call lo_NewNodeInfo.SetData(ll_Index, lo_NodeInfo.GetData(ll_StartDataIndex + ll_Index))
          Next
          lo_NewNodeInfo.DirectImages = True
          lo_NewNodeInfo.TextIndex = 1
          lo_NewNodeInfo.ParentIDIndex = -1
          lo_NewNodeInfo.ImageValue = mv_Images(ll_Level)
          lo_NewNodeInfo.SelectedImageValue = mv_SelectedImages(ll_Level)
          lo_NewNodeInfo.ml_Level = ll_Level
          lo_NewNodeInfo.LoadType = LoadTypeNone
          
          Set lo_Node = AddNodeWithNodeInfo(lao_LastNodes(ll_Level), lo_NewNodeInfo)
          lav_LastIDs(ll_Level) = lo_NodeInfo.GetData(ll_StartDataIndex)
          Set lao_LastNodes(ll_Level + 1) = lo_Node
          
        End If
        End If
      Next
    Next
  End If
  Call InitNodeHandles(Nothing)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadModeTree")
#End If
End Function
'load branch in one go
Private Function LoadModeBranch(ByVal lo_NodeInfos As Collection, lo_ParentNode As Node) As Boolean
Dim ll_ChildIndex As Long
Dim lb_ParentFound As Boolean
Dim lo_ParentNodeInfo As NodeInfo
Dim lo_ChildNodeInfo As NodeInfo
Dim lo_Node As Node
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadModeBranch")
#End If
  LoadModeBranch = True
  If Not (lo_ParentNode Is Nothing) Then NodeInfo(lo_ParentNode).mb_ChildrenLoaded = True
  
  Do
    lb_ParentFound = False
    For ll_ChildIndex = 1 To lo_NodeInfos.Count
      Set lo_ChildNodeInfo = lo_NodeInfos(ll_ChildIndex)
    
      If (lo_ParentNode Is Nothing) And (lo_ChildNodeInfo.ParentIDValue = RootValue) Then
        Call AddNodeWithNodeInfo(Nothing, lo_ChildNodeInfo)
        lb_ParentFound = True
      Else
        For Each lo_Node In TreeViewCtrl.Nodes
          If (lo_ParentNode Is Nothing) Or (lo_ParentNode Is lo_Node) Or (IsParentNode(lo_Node, lo_ParentNode)) Then
            Set lo_ParentNodeInfo = NodeInfo(lo_Node)
            If lo_ParentNodeInfo.IDValue = lo_ChildNodeInfo.ParentIDValue Then
              Call AddNodeWithNodeInfo(lo_Node, lo_ChildNodeInfo)
              lb_ParentFound = True
              Exit For
            End If
          End If
        Next lo_Node
      End If
      If lb_ParentFound Then
        lo_NodeInfos.Remove (ll_ChildIndex)
        Exit For
      End If
    Next ll_ChildIndex
  Loop Until Not lb_ParentFound
  
  If lo_NodeInfos.Count <> 0 Then LoadModeBranch = False
  Call InitNodeHandles(lo_ParentNode)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadModeBranch")
#End If
End Function
'load level in one go
Private Function LoadModeLevel(ByVal lo_NodeInfos As Collection, ll_Level As Long) As Boolean
Dim lo_ChildNodeInfo As NodeInfo, lo_ParentNodeInfo As NodeInfo
Dim lo_ParentNode As Node
Dim lb_ParentFound As Boolean
Dim lo_ParentCollection As New Collection
Dim lv_LastParentID As Variant
  
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadModeLevel")
#End If
  LoadModeLevel = True
  
  If ll_Level = 0 Then
    For Each lo_ChildNodeInfo In lo_NodeInfos
      Call AddNodeWithNodeInfo(Nothing, lo_ChildNodeInfo)
    Next
  Else
    For Each lo_ParentNode In TreeViewCtrl.Nodes
      If GetNodeLevel(lo_ParentNode) = ll_Level - 1 Then
        Call lo_ParentCollection.Add(NodeInfo(lo_ParentNode))
      End If
    Next

    lv_LastParentID = Null
    For Each lo_ChildNodeInfo In lo_NodeInfos
      lb_ParentFound = False
      For Each lo_ParentNodeInfo In lo_ParentCollection
        If StrComp(lo_ParentNodeInfo.IDValue, lo_ChildNodeInfo.ParentIDValue, vbBinaryCompare) = 0 Then
          Call AddNodeWithNodeInfo(lo_ParentNodeInfo.mo_Node, lo_ChildNodeInfo)
          lb_ParentFound = True
          Exit For
        End If
      Next
      If Not lb_ParentFound Then LoadModeLevel = False
    Next
  End If
  Call InitNodeHandles(Nothing)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadModeLevel")
#End If
End Function
'load childs in one go
Private Function LoadModeChilds(lo_NodeInfos As Collection, lo_ParentNode As Node) As Boolean
Dim lo_ChildNodeInfo As NodeInfo

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LoadModeChilds")
#End If
  LoadModeChilds = True
  If Not (lo_ParentNode Is Nothing) Then NodeInfo(lo_ParentNode).mb_ChildrenLoaded = True
  
  For Each lo_ChildNodeInfo In lo_NodeInfos
    Call AddNodeWithNodeInfo(lo_ParentNode, lo_ChildNodeInfo)
  Next
  If lo_ParentNode Is Nothing Then
    Call InitNodeHandles(lo_ParentNode)
  Else
    Call InitNodeHandles(lo_ParentNode, NodeInfo(lo_ParentNode).ml_State)
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LoadModeChilds")
#End If
End Function
'try to find node with lv_IDValue in our treeview
Private Function LocateNodeInTree(lv_IDValue As Variant, Optional lo_ParentNode As Node = Nothing, Optional ll_Level As Long = -1) As Node
Dim lo_Node As Node
Dim lb_NodeFound As Boolean

  If Not (IsEmpty(lv_IDValue) Or IsNull(lv_IDValue)) Then
    Set LocateNodeInTree = Nothing
    For Each lo_Node In TreeViewCtrl.Nodes
      lb_NodeFound = False
      If StrComp(NodeInfo(lo_Node).IDValue, lv_IDValue, vbBinaryCompare) = 0 Then
        lb_NodeFound = True
        If lb_NodeFound And (Not (lo_ParentNode Is Nothing)) And (Not IsParentNode(lo_Node, lo_ParentNode)) Then lb_NodeFound = False
        If lb_NodeFound And (ll_Level >= 0) And (ll_Level <> GetNodeLevel(lo_Node)) Then lb_NodeFound = False
        If lb_NodeFound Then
          Set LocateNodeInTree = lo_Node
          Exit For
        End If
      End If
    Next
  End If
End Function
'
Private Function LocateNodeInDB(ByVal lo_NodeInfo As NodeInfo, Optional ByVal lb_Select As Boolean = True, Optional ByVal lb_Expand As Boolean = False) As Node
Dim ll_Level As Long, ll_MaxLevel As Long
Dim lo_Node As Node
Dim lv_Data As Variant

  Set LocateNodeInDB = Nothing
  If Not (lo_NodeInfo Is Nothing) Then
    If Not (lo_NodeInfo.mo_Node Is Nothing) Then
      Set SelectedItem = lo_NodeInfo.mo_Node
      Set LocateNodeInDB = lo_NodeInfo.mo_Node
    Else
      Set lo_Node = Nothing
      ll_MaxLevel = Min(Levels - 1, lo_NodeInfo.Length)
      For ll_Level = 0 To ll_MaxLevel
        lv_Data = lo_NodeInfo.GetData(ll_Level)
        If IsClear(lv_Data) Then Exit For
        Set lo_Node = LocateNodeInTree(lv_Data, lo_Node, ll_Level)
        If lo_Node Is Nothing Then Exit For
        Call TreeViewCtrl_Expand(lo_Node)
        If lb_Expand Then lo_Node.Expanded = True
      Next
      Set LocateNodeInDB = lo_Node
      If (Not (lo_Node Is Nothing)) And lb_Select Then Set SelectedItem = lo_Node
    End If
  End If
End Function

'get grid request for one node - either cursor or SQL request
Private Function GetNodeGridRequest(ByVal lo_Node As Node, Optional ByVal lb_CountRequest As Boolean = False) As Variant
Dim lv_Request As Variant
Dim lo_NodeInfo As NodeInfo

Dim ll_Temp As Long, ll_Result As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:GetNodeGridRequest")
#End If
  If Not (lo_Node Is Nothing) Then
    If IsValidCursor(mav_GridRequests(0)) Then
      If Not (mo_Db Is Nothing) Then
        ll_Result = mav_GridRequests(0)
        Do
          Set lo_NodeInfo = NodeInfo(lo_Node)
          ll_Temp = ll_Result
          ll_Result = mo_Db.Filter(ll_Temp, ms_KeyFieldsNames(lo_NodeInfo.ml_Level), "=", lo_NodeInfo.GetData(0))
          If ll_Temp <> mav_GridRequests(0) Then Call mo_Db.Close(ll_Temp)
          Set lo_Node = lo_Node.Parent
        Loop Until lo_Node Is Nothing
        GetNodeGridRequest = ll_Result
      End If
    Else
      If lb_CountRequest Then
        GetNodeGridRequest = ReplaceParameters(mav_GridCountRequests(NodeInfo(lo_Node).ml_Level), NodeInfo(lo_Node))
      Else
        GetNodeGridRequest = ReplaceParameters(mav_GridRequests(NodeInfo(lo_Node).ml_Level), NodeInfo(lo_Node))
      End If
      'ls_Cond = ""
      'Do
      '  Set lo_NodeInfo = NodeInfo(lo_Node)
      '  ls_Cond = ls_Cond & "(" & ms_KeyFieldsNames(lo_NodeInfo.ml_Level) & "=$0$)"
      '  ls_Cond = ReplacePlaceHolder(ls_Cond, 0, lo_NodeInfo.GetData(0))
      '  Set lo_Node = lo_Node.Parent
      '  If Not (lo_Node Is Nothing) Then ls_Cond = ls_Cond + " AND "
      'Loop Until lo_Node Is Nothing
      'GetNodeGridRequest = ReplacePlaceHolder(GridRequest, 0, ls_Cond, True)
    End If
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:GetNodeGridRequest")
#End If
End Function

Private Sub InitNodeHandles(ByVal lo_Node As Node, _
  Optional ByVal ll_State As Long = TVI_STATE_UNCHECKED, Optional ByVal lb_Children As Boolean = False)
Dim ll_Index As Long
Dim hItem As Long

  If lo_Node Is Nothing Then
    Set lo_Node = GetFirstRoot                    'Get root (first child of nothing)
    If lo_Node Is Nothing Then Exit Sub
    hItem = TreeView_GetRoot(TreeViewCtrl.hwnd)
  Else
    hItem = NodeInfo(lo_Node).hItem
    If hItem = 0 Then Exit Sub
    
    Set lo_Node = lo_Node.Child                   'get first child of lo_Node
    hItem = TreeView_GetChild(TreeViewCtrl.hwnd, hItem)
  End If
  
  While (Not (lo_Node Is Nothing)) And (hItem <> 0)                           'iterate all siblings (childs of input parameter) and set hItem
    If NodeInfo(lo_Node).hItem = 0 Then
      NodeInfo(lo_Node).hItem = hItem
      Call SetNodeState(lo_Node, ll_State, lb_Children)
    End If
    Call InitNodeHandles(lo_Node, ll_State, lb_Children)
    hItem = TreeView_GetNextSibling(TreeViewCtrl.hwnd, hItem)
    Set lo_Node = lo_Node.Next
  Wend

End Sub

Public Sub SetNodeState(ByVal lo_Node As Node, ByVal ll_State As Long, Optional ByVal lb_Children As Boolean = False)
Dim ut_TVItem As tvItem

    ut_TVItem.mask = TVIF_HANDLE Or TVIF_STATE
    ut_TVItem.hItem = NodeInfo(lo_Node).hItem
    ut_TVItem.stateMask = TVIS_STATEIMAGEMASK
    ut_TVItem.state = ll_State * (2 ^ 12)

    Call TreeView_SetItem(TreeViewCtrl.hwnd, ut_TVItem)
    NodeInfo(lo_Node).ml_State = ll_State
    
    If lb_Children Then
      Set lo_Node = lo_Node.Child
      While Not (lo_Node Is Nothing)
        Call SetNodeState(lo_Node, ll_State, lb_Children)
        Set lo_Node = lo_Node.Next
      Wend
    End If
End Sub

Public Sub SetParentState(ByVal lo_ChildNode As Node)
Dim lo_ParentNode As Node, lo_Node As Node
Dim lb_AllChecked As Boolean, lb_AllUnchecked As Boolean
Dim ll_State As Long

  Set lo_ParentNode = lo_ChildNode.Parent
  If Not (lo_ParentNode Is Nothing) Then
    lb_AllChecked = True
    lb_AllUnchecked = True
    Set lo_Node = lo_ParentNode.Child
    While Not (lo_Node Is Nothing)
      ll_State = NodeInfo(lo_Node).ml_State
      If (ll_State = TVI_STATE_CHECKED) Then
        lb_AllUnchecked = False
      ElseIf (ll_State = TVI_STATE_UNCHECKED) Then
        lb_AllChecked = False
      Else
        lb_AllUnchecked = False
        lb_AllChecked = False
      End If
      Set lo_Node = lo_Node.Next
    Wend
    Set lo_Node = Nothing
    If lb_AllUnchecked Then
      Call SetNodeState(lo_ParentNode, TVI_STATE_UNCHECKED)
    ElseIf lb_AllChecked Then
      Call SetNodeState(lo_ParentNode, TVI_STATE_CHECKED)
    Else
      Call SetNodeState(lo_ParentNode, TVI_STATE_GRAYED)
    End If
    Call SetParentState(lo_ParentNode)
    Set lo_ParentNode = Nothing
  End If
End Sub
'get
Public Function SelectNodeGridRequest(ll_RowIndex As Long, ll_Level As Long) As Boolean
Dim lb_Result As Boolean
Dim lo_Node As Node
Dim lo_NodeInfo As New NodeInfo
Dim ll_Index As Long
Dim lv_Field As Variant

  lb_Result = False
  If IsValidCursor(mav_GridRequests(0)) And (ll_Level < Levels) And (ll_Level >= 0) Then
    Call lo_NodeInfo.SetNodeDataSize(ll_Level)
    For ll_Index = 0 To ll_Level
      lv_Field = ms_KeyFieldsNames(ll_Index)
      Call lo_NodeInfo.SetData(ll_Index, mo_Db.GetFieldsAt(mav_GridRequests(0), ll_RowIndex, lv_Field))
    Next
    Set lo_Node = LocateNodeInDB(lo_NodeInfo)
    If Not (lo_Node Is Nothing) Then lb_Result = True
  End If
  SelectNodeGridRequest = lb_Result
End Function
'TREEVIEW MACROS

' Retrieves the handle to the normal or state image list associated with a tree-view control.
' Returns the handle to the image list.

Private Function TreeView_GetImageList(hwnd As Long, iImage As Long) As Long
  TreeView_GetImageList = SendMessage(hwnd, TVM_GETIMAGELIST, ByVal iImage, 0)
End Function

' Sets the normal or state image list for a tree-view control and redraws the control using the new images.
' Returns the handle to the previous image list, if any, or 0 otherwise.

Private Function TreeView_SetImageList(hwnd As Long, himl As Long, iImage As Long) As Long
  TreeView_SetImageList = SendMessage(hwnd, TVM_SETIMAGELIST, ByVal iImage, ByVal himl)
End Function


' Retrieves the topmost or very first item of the tree-view control.
' Returns the handle to the item if successful or 0 otherwise.

Private Function TreeView_GetRoot(hwnd As Long) As Long
  TreeView_GetRoot = TreeView_GetNextItem(hwnd, 0, TVGN_ROOT)
End Function

' Removes an item from a tree-view control.
' Returns TRUE if successful or FALSE otherwise.

Private Function TreeView_DeleteItem(hwnd As Long, hItem As Long) As Boolean
  TreeView_DeleteItem = SendMessage(hwnd, TVM_DELETEITEM, 0, ByVal hItem)
End Function

' Retrieves the currently selected item.
' Returns the handle to the item if successful or 0 otherwise.

Private Function TreeView_GetSelection(hwnd As Long) As Long
  TreeView_GetSelection = TreeView_GetNextItem(hwnd, 0, TVGN_CARET)
End Function

' Old docs say returns zero if successful or - 1 otherwise.
' New docs say returns TRUE if successful, or FALSE otherwise

Private Function TreeView_SetItem(hwnd As Long, pitem As tvItem) As Boolean
  TreeView_SetItem = SendMessage(hwnd, TVM_SETITEM, 0, pitem)
End Function

' Determines the location of the specified point relative to the client area of a tree-view control.
' Returns the handle to the tree-view item that occupies the specified point or NULL if no item
' occupies the point.

Private Function TreeView_HitTest(hwnd As Long, lpht As TVHITTESTINFO) As Long
  TreeView_HitTest = SendMessage(hwnd, TVM_HITTEST, 0, lpht)
End Function

' Retrieves the first child item. The hitem parameter must be NULL.
' Returns the handle to the item if successful or 0 otherwise.

Private Function TreeView_GetChild(hwnd As Long, hItem As Long) As Long
  TreeView_GetChild = TreeView_GetNextItem(hwnd, hItem, TVGN_CHILD)
End Function

' Retrieves the next sibling item.
' Returns the handle to the item if successful or 0 otherwise.

Private Function TreeView_GetNextSibling(hwnd As Long, hItem As Long) As Long
  TreeView_GetNextSibling = TreeView_GetNextItem(hwnd, hItem, TVGN_NEXT)
End Function

' Retrieves the tree-view item that bears the specified relationship to a specified item.
' Returns the handle to the item if successful or 0 otherwise.

Private Function TreeView_GetNextItem(hwnd As Long, hItem As Long, flag As Long) As Long
  TreeView_GetNextItem = SendMessage(hwnd, TVM_GETNEXTITEM, ByVal flag, ByVal hItem)
End Function

Public Property Get FindShow() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindShow_Get")
#End If
  
  FindShow = frmFind.Visible
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindShow_Get")
#End If
End Property

Public Property Let FindShow(ByVal lb_NewValue As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FindShow_Let")
#End If
  
  mb_FirstFind = True
  frmFind.Visible = lb_NewValue
  Call UserControl_Resize
  If frmFind.Visible Then Call txtFind.SetFocus
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FindShow_Let")
#End If
End Property

Public Property Get Language() As String
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Language_Get")
#End If
  Language = ms_LanguageCode
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Language_Get")
#End If
End Property

Public Property Let Language(ByVal ls_NewValue As String)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Language_Let")
#End If

  ms_LanguageCode = ls_NewValue
  Call InitLevelNames
  MenuCheckBoxes.Caption = Translate(cstCheckBoxes, ms_LanguageCode)
  MenuCheckAll.Caption = Translate(cstCheckAll, ms_LanguageCode)
  MenuUnCheckAll.Caption = Translate(cstUnCheckAll, ms_LanguageCode)
  MenuCount.Caption = Translate(cstCount, ms_LanguageCode)
  MenuInfo.Caption = Translate(cstMoreInfo, ms_LanguageCode)
  MenuFind.Caption = Translate(cstFind, ms_LanguageCode)
  lblLevel.Caption = Translate(cstLevel, ms_LanguageCode)
  lblText.Caption = Translate(cstText, ms_LanguageCode)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Language_Let")
#End If
End Property
'level names are initially set to text "Level" & ll_Level
Private Sub InitLevelNames()
Dim ll_Level As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:InitLevelNames")
#End If
  Call cmbLevel.Clear
  Call cmbLevel.AddItem(Translate(cstAll, ms_LanguageCode))
  cmbLevel.ItemData(cmbLevel.NewIndex) = -1
  For ll_Level = 0 To Levels - 1
    Call cmbLevel.AddItem(Translate(cstLevel, ms_LanguageCode) & " " & CStr(ll_Level + 1))
    cmbLevel.ItemData(cmbLevel.NewIndex) = ll_Level
  Next
  cmbLevel.ListIndex = 0
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:InitLevelNames")
#End If
End Sub
'set names which will be displayed in combobox for level selection
Public Property Let LevelNames(ByVal lv_LevelNames As Variant)
Dim ll_Level As Long

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:LevelNames_Let")
#End If
  Call cmbLevel.Clear
  Call cmbLevel.AddItem(Translate(cstAll, ms_LanguageCode))
  cmbLevel.ItemData(cmbLevel.NewIndex) = -1
  If IsArray(lv_LevelNames) Then
    For ll_Level = 0 To UBound(lv_LevelNames)
      Call cmbLevel.AddItem(lv_LevelNames(ll_Level))
      cmbLevel.ItemData(cmbLevel.NewIndex) = ll_Level
    Next
    cmbLevel.ListIndex = 0
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:LevelNames_Let")
#End If
End Property

'Enabled property
Public Property Get Enabled() As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Enabled_Get")
#End If
  
  Enabled = TreeViewCtrl.Enabled
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Enabled_Get")
#End If
End Property

Public Property Let Enabled(ByVal vNewValue As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Enabled_Let")
#End If
  
  TreeViewCtrl.Enabled = vNewValue
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Enabled_Let")
#End If
End Property

Public Sub ExpandNode(ByVal lo_Node As Node)

  If Not (lo_Node Is Nothing) Then
    Call TreeViewCtrl_Expand(lo_Node)
    lo_Node.Expanded = True
  End If
End Sub

Public Property Get Font() As StdFont
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:Font_Get")
#End If

  Set Font = TreeViewCtrl.Font
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:Font_Get")
#End If
End Property

Public Property Get FontCombo() As StdFont
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FontCombo_Get")
#End If

  Set FontCombo = cmbLevel.Font
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FontCombo_Get")
#End If
End Property

Public Property Get FontText() As StdFont
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:FontText_Get")
#End If
  
  Set FontText = txtFind.Font
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:FontText_Get")
#End If
End Property

'**************************************************************************************************************
'turn of on mouse pointer sand hour glass, use counter
Private Static Sub SetMousePointer(lb_Enable As Boolean)
Dim li_Count As Integer
Dim li_OldPointer As Integer

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SetMousePointer")
#End If
  If lb_Enable Then
    If li_Count > 0 Then li_Count = li_Count - 1
    If li_Count <= 0 Then
      'set back old state
      Screen.MousePointer = li_OldPointer
    End If
  Else
    li_Count = li_Count + 1
    'remember state of pointer before first change
    If li_Count = 1 Then li_OldPointer = Screen.MousePointer
    Screen.MousePointer = vbHourglass
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SetMousePointer")
#End If
End Sub
'turn on off treeview control redrawing

Public Static Sub SetRedraw(hwnd As Long, lb_Enable As Boolean)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:SetRedraw")
#End If
  
Dim li_Count As Integer

  If lb_Enable Then
    If li_Count > 0 Then li_Count = li_Count - 1
    If li_Count <= 0 Then TreeViewCtrl.Visible = True
  Else
    li_Count = li_Count + 1
    TreeViewCtrl.Visible = False
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:SetRedraw")
#End If
End Sub

Public Sub EnableToolTips()
Dim ll_Style As Long
   ' Get the TreeView's window style
   ll_Style = GetWindowLong(TreeViewCtrl.hwnd, GWL_STYLE)

   ' If the TreeView does not have the TVS_NOTOOLTIPS bit set,
   ' set it, removing and destroying the TreeView's tooltip window.
   If (ll_Style And TVS_NOTOOLTIPS) = False Then
      Call SetWindowLong(TreeViewCtrl.hwnd, GWL_STYLE, _
                         ll_Style Or TVS_NOTOOLTIPS)
   End If

   ' Tell the TreeView to create a new tooltip window, whose parent
   ' window will now become the top level client app window
   Call SetWindowLong(TreeViewCtrl.hwnd, GWL_STYLE, _
                      ll_Style And Not TVS_NOTOOLTIPS)
  
End Sub

Private Function IsValidCursor(lv_Request As Variant) As Boolean
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:IsValidCursor")
#End If
  
  If (VarType(lv_Request) = vbLong) Or VarType(lv_Request) = vbInteger Then
    IsValidCursor = True
  ElseIf (VarType(lv_Request) = vbString) And IsNumeric(lv_Request) Then
    IsValidCursor = True
  End If

#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:IsValidCursor")
#End If
End Function

'clear collection (there is no standard method like lo_Collection.Clear ???? )
Private Sub ClearCollection(lo_Collection As Collection)
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(True, "ArmTreeView:ClearCollection")
#End If
  
  If Not (lo_Collection Is Nothing) Then
    While lo_Collection.Count > 0
      lo_Collection.Remove (1)
    Wend
  End If
#If CompDebugTV Then
  Call mo_Trace.WriteTraceProc(False, "ArmTreeView:ClearCollection")
#End If
End Sub

'get maximum of those two parameters
Private Function Max(lv_Op1 As Variant, lv_Op2 As Variant) As Variant
  Max = IIf(lv_Op1 >= lv_Op2, lv_Op1, lv_Op2)
End Function
'get minimum of those two parameters
Private Function Min(lv_Op1 As Variant, lv_Op2 As Variant) As Variant
  Min = IIf(lv_Op1 <= lv_Op2, lv_Op1, lv_Op2)
End Function

'copy array from source to destination - checking boundaries
Private Sub SetVarArray(lv_Src As Variant, lv_Dst As Variant)
  Dim ll_Index As Long
    
  If IsArray(lv_Dst) Then
    If IsArray(lv_Src) Then
      For ll_Index = 0 To Min(UBound(lv_Src), UBound(lv_Dst))
        lv_Dst(ll_Index) = lv_Src(ll_Index)
      Next
    Else
      lv_Dst(0) = lv_Src
    End If
  End If
End Sub

Private Function IsClear(lv_Value As Variant) As Boolean

  IsClear = True
  If lv_Value = Empty Then Exit Function
  If lv_Value = Null Then Exit Function
  If VarType(lv_Value) = vbString Then
    If lv_Value = "" Then Exit Function
  Else
    If lv_Value = 0 Then Exit Function
  End If
  IsClear = False
End Function

'replace PlaceHolder  - format $X$, by value, where X is ll_Index
'ls_Str string in which we find and replace placeholder
'll_Index - index of placeholder
'lv_Value - value for which our placeholder will be replaced
'lb_OverrideNum - nevermind of value type, don't use aphostrophes
Private Function ReplacePlaceHolder(ByVal ls_Str As String, ByVal ll_Index As Long, lv_Value As Variant, Optional lb_OverrideNum As Boolean = False) As String
  If (VarType(lv_Value) <> vbString) Or lb_OverrideNum Then
    ReplacePlaceHolder = Replace(ls_Str, PHolder & ll_Index & PHolder, lv_Value)
  Else
    ReplacePlaceHolder = Replace(ls_Str, PHolder & ll_Index & PHolder, "'" & lv_Value & "'")
  End If
End Function

Public Property Get PHolder() As String
  PHolder = CH_PHOLDER
End Property

Public Property Get LDelimiter() As Variant
  LDelimiter = CH_LDELIMIT
End Property

Private Function Translate(ByVal lt_Const As UIStringConstants, ByVal lc As String) As String

  Select Case lt_Const
  Case cstFindNext
    Translate = Switch(lc = "G", "Folgende", _
                        lc = "D", "Zoek volgende", _
                        lc = "F", "Chercher suivant", _
                        lc = "I", "Ricerca seguente", _
                        lc = "S", "Buscar el siguiente", _
                        True, "Find next")
  Case cstFind
    Translate = Switch(lc = "G", "Finden", _
                        lc = "D", "Zoek", _
                        lc = "F", "Chercher", _
                        lc = "I", "Cercare", _
                        lc = "S", "Buscar", _
                        True, "Find...")
  Case cstMoreInfo
    Translate = Switch(lc = "G", "Mehr Information", _
                        lc = "D", "Meer info", _
                        lc = "F", "Plus d 'infos", _
                        lc = "I", "Altre info", _
                        lc = "S", "Mas informaciєn", _
                        True, "More info...")
  Case cstCancel
    Translate = Switch(lc = "G", "Schlie▀en", _
                        lc = "D", "Annuleer", _
                        lc = "F", "Annuler", _
                        lc = "I", "Cancellare", _
                        lc = "S", "Cancelar", _
                        True, "Cancel")
  Case cstLevel
    Translate = Switch(lc = "G", "Niveau", _
                        lc = "D", "Niveau", _
                        lc = "F", "Niveau", _
                        lc = "I", "livello", _
                        lc = "S", "Nivel", _
                        True, "Level")
  Case cstText
    Translate = Switch(lc = "G", "Text", _
                        lc = "D", "Tekst", _
                        lc = "F", "Texte", _
                        lc = "I", "Testo", _
                        lc = "S", "Texto", _
                        True, "Text")
  Case cstAll
    Translate = Switch(lc = "G", "Alles", _
                        lc = "D", "Alles", _
                        lc = "F", "Tout", _
                        lc = "I", "Tutto", _
                        lc = "S", "Todo", _
                        True, "All")
  Case cstCount
    Translate = Switch(lc = "G", "Anzahl", _
                        lc = "D", "Tel op", _
                        lc = "F", "Compter", _
                        lc = "I", "Conteggio", _
                        lc = "S", "Contar", _
                        True, "Count")
  Case cstCountAll
    Translate = Switch(lc = "G", "Alle item", _
                        lc = "D", "Alles", _
                        lc = "F", "Tous les produits", _
                        lc = "I", "Tutto", _
                        lc = "S", "Todos los Items", _
                        True, "All items:")
  Case cstCountChildren
    Translate = Switch(lc = "G", "Nфchstes Niveau", _
                        lc = "D", "Volgend niveau", _
                        lc = "F", "Niveau suivant", _
                        lc = "I", "Prossimo livello", _
                        lc = "S", "Prєximo Nivel", _
                        True, "Next level")
  Case cstCountChecked
    Translate = Switch(lc = "G", "▄berpr№ftete Items", _
                        lc = "D", "Items Checked", _
                        lc = "F", "Products Selectionnes", _
                        lc = "I", "Punti verificati", _
                        lc = "S", "Items chequeados", _
                        True, "Items checked:")
  Case cstCheckBoxes
    Translate = Switch(lc = "G", "Checkbox", _
                        lc = "D", "Gebruik check box functie", _
                        lc = "F", "Utilse les check boxes", _
                        lc = "I", "Seleziona opzioni", _
                        lc = "S", "Utilice las casillas de chequeo", _
                        True, "Use checkboxes")
  Case cstCheckAll
    Translate = Switch(lc = "G", "Check all", _
                        lc = "D", "Check all", _
                        lc = "F", "Check all", _
                        lc = "I", "Check all", _
                        lc = "S", "Check all", _
                        True, "Check all")
  Case cstUnCheckAll
    Translate = Switch(lc = "G", "Uncheck all", _
                        lc = "D", "Uncheck all", _
                        lc = "F", "Uncheck all", _
                        lc = "I", "Uncheck all", _
                        lc = "S", "Uncheck all", _
                        True, "Uncheck all")
  Case cstNotFound
    Translate = Switch(lc = "G", "Nicht gefunden", _
                        lc = "D", "Niet gevonden", _
                        lc = "F", "Pas trouvщ", _
                        lc = "I", "Non trovato", _
                        lc = "S", "No encontrado", _
                        True, "Not found")
  End Select

End Function


Public Property Let Visible(ByVal aValue As Boolean)
    UserControl.Extender.Visible = aValue
End Property
Public Property Get Visible() As Boolean
    Visible = UserControl.Extender.Visible
End Property


