B4J Question Tree list for Directory with checkbox and more than 1 levels

Peter Lewis

Active Member
Licensed User
Longtime User
Hi All

I want to have a dialog box that allows me to choose Directories / subdirectories and once tagged then make a sql file of the full path of each file in those directories / sub directories / Sub / Sub / Sub ect

I can recursivley get all the file names from a directory but sometimes do not want all of the sub directories of that directory.

I hope this is makes sense.


I have been trying all different methods for a while and taking code from all over this forum to try and make this work.

I want to firstly choose what Drive I want to scan but I have not seen anything that might help, so I have come to the conclusion to put a field in the Settings of the program.

The problem is really the extra levels. I can get the Root and one level working ok except it does show the directory name in the 1st level of the level I have entered. If I do not put something there then the tree option in TreeView does not appear.

One way to solve this is maybe to ready the complete drive and build all the directory tree before displaying anything. At the moment we are only reading the next level directory tree when we open it in TreeView.


If I could determine which level I was on I could then change this to get a directory of the next level
B4X:
FolderList("i:\" & ti.Text)




B4X:
    #MainFormWidth: 800
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private TreeView1 As TreeView
    Dim folders As List
   
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("dirtree") 'Load the layout file.
    MainForm.Show
    TreeView1.SetCheckBoxesMode
   
    FolderList("i:\")
    For i = 0 To folders.Size-1
        Dim ti As CheckboxTreeItem
             ti.Initialize("ti", folders.Get(i))
        Dim cti As CheckboxTreeItem
             cti.Initialize("cti", folders.Get(i))
        Dim ctp As CheckboxTreeItem
                ctp.Initialize("cti", folders.Get(i))
        cti.Children.Add(ctp) 'add the child of the child
                   ti.Children.Add(cti) 'add the child
                 TreeView1.Root.Children.Add(ti)
    Next
     End Sub
    
    Sub FolderList(folder As String) As List
   
   folders.Initialize
   folders.Clear
   For Each f As String In File.ListFiles(folder)
     If File.IsDirectory(folder, f) = True Then
         folders.Add(f)
     Dim a As String = f
     Log(a)
     Else
   End If
   Next
  
   Return folders
  
End Sub

Sub TI_ExpandedChanged(Expanded As Boolean)
    If Expanded = True Then
        Dim ti As CheckboxTreeItem = Sender

    FolderList("i:\" & ti.Text)
    Log("ti.text ="&ti.Text)
        For i = 0 To folders.Size-1
            Dim ti1 As CheckboxTreeItem

    ti1.Initialize("ti1", folders.Get(i))
        Dim cti1 As CheckboxTreeItem
   cti1.Initialize("cti1",folders.Get(i))
            Dim ctp1 As CheckboxTreeItem
    ctp1.Initialize("cti1", folders.Get(i))
                cti1.Children.Add(ctp1) 'add the child of the child
                       ti.Children.Add(cti1) 'add the child
                   TreeView1.Root.Children.Add(ti1)
      Next
     Else
                  folders.Clear
        End If

End Sub



Sub TreeView1_SelectedItemChanged(SelectedItem As TreeItem)
   
End Sub
 

Peter Lewis

Active Member
Licensed User
Longtime User
Well I have managed to fill 2 levels of Directory info.

And I can see that the check works on both levels.

Root is the first level

The problem I am faced with is the check for the 2nd and third level come from the same routine. This is apparent when I try to open up the fourth level and the system crashes as it does not get to the correct SUB.

Here is the new code. I am sure there is a lot of wasted code as I have been trying so many options

B4X:
#Region  Project Attributes
    #MainFormWidth: 800
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private TreeView1 As TreeView
    Dim folders As List
    Dim fPath As String
    Dim pPath As String
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("dirtree") 'Load the layout file.
    MainForm.Show
    TreeView1.SetCheckBoxesMode
  
    FolderList("i:\")
    For i = 0 To folders.Size-1
        Dim ti As CheckboxTreeItem
             ti.Initialize("ti", folders.Get(i))
        Dim cti As CheckboxTreeItem
             cti.Initialize("cti", folders.Get(i))
        Dim ctp As CheckboxTreeItem
             ctp.Initialize("ctp", folders.Get(i))
              
              
                 
         TreeView1.Root.Children.Add(ti) ' add the parent
        ti.Children.Add(cti) 'add the child
        cti.Children.Add(ctp) 'add the child of the child
    Next
     End Sub
   
    Sub FolderList(folder As String) As List
    Log("This is the current Folder "&folder)
   folders.Initialize
   folders.Clear
   For Each f As String In File.ListFiles(folder)
     If File.IsDirectory(folder, f) = True Then
         folders.Add(f)
     Dim a As String = f
     Log(a)
     Else
   End If
   Next
 
   Return folders
 
End Sub

Sub TI_ExpandedChanged(Expanded As Boolean)
    If Expanded = True Then
        Dim TIA As CheckboxTreeItem = Sender
      
    FolderList("i:\" & TIA.Text&"\")
    Log("ti.text ="&TIA.Text)
        fPath=TIA.Text
    For i = 0 To folders.Size-1

                Dim ti1 As CheckboxTreeItem
            ti1.Initialize("ti1", folders.Get(i))
                Dim cti1 As CheckboxTreeItem
            cti1.Initialize("cti1",folders.Get(i))
                Dim ctp1 As CheckboxTreeItem
            ctp1.Initialize("ctp1", folders.Get(i))
          
                cti1.Children.Add(ctp1) 'add the child of the child
                TIA.Children.Add(cti1) 'add the child
                TreeView1.Root.Children.Add(ti1)
     Next
     Else
        folders.Clear
    End If

End Sub



Sub CTI1_ExpandedChanged(Expanded As Boolean)
    If Expanded = True Then
        Dim CTI1A As CheckboxTreeItem = Sender
        Log("CTI1 expanded changed ")
    FolderList("i:\" & fPath &"\"&CTI1A.Text&"\")
    Log(" CTI1 cti.text ="&CTI1A.Text)
        pPath=CTI1A.text
        Log("pPath  = "&pPath)
        For i = 0 To folders.Size-1
  
        Dim ti1 As CheckboxTreeItem
    ti1.Initialize("ti1", folders.Get(i))
        Dim CTI1 As CheckboxTreeItem
    CTI1.Initialize("CTI1",folders.Get(i))
        Dim ctp1 As CheckboxTreeItem
    ctp1.Initialize("ctp1", folders.Get(i))
  
            TreeView1.Root.Children.Add(ti1)
            CTI1A.Children.Add(CTI1) 'add the child
            CTI1.Children.Add(ctp1) 'add the child of the child
             
                 
      Next
     Else
        folders.Clear
    End If

End Sub

Sub CTP1_ExpandedChanged(Expanded As Boolean)
    If Expanded = True Then
        Dim CTP1A As CheckboxTreeItem = Sender
        Log("CTP1 expanded changed ")
    FolderList("i:\" & fPath &"\"&pPath&"\"&CTP1A.Text&"\")
    Log("ctp1.text ="&CTP1A.Text)
        For i = 0 To folders.Size-1
          
            Dim ti1 As CheckboxTreeItem
    ti1.Initialize("ti1", folders.Get(i))
            Dim cti1 As CheckboxTreeItem
    cti1.Initialize("cti1",folders.Get(i))
            Dim CTP1 As CheckboxTreeItem
    CTP1.Initialize("ctp1", folders.Get(i))
  
                TreeView1.Root.Children.Add(ti1)
                CTP1A.Children.Add(cti1) 'add the child
                 cti1.Children.Add(CTP1) 'add the child of the child
      Next
     Else
                  folders.Clear
        End If

End Sub




Sub TI_CheckedChange(Ticked As Boolean)
    If Ticked = True Then
    Log("tick change")
    Dim ti As CheckboxTreeItem = Sender
    Log(ti.Text & ": " & Ticked)
    Else
        folders.Clear
        End If
End Sub

Sub TI1_CheckedChange(Ticked As Boolean)
    If Ticked = True Then
    Log("tick wrong change")
    Dim ti1 As CheckboxTreeItem = Sender
    Log(ti1.Text & ": " & Ticked)
    Else
        folders.Clear
        End If
End Sub

Sub CTI1_CheckedChange(Ticked As Boolean)
  
    Log("tick change 2nd row")
    Dim cti As CheckboxTreeItem = Sender
    Log(cti.Text & ": " & Ticked)
End Sub

Sub CTP1_CheckedChange(Ticked As Boolean)
  
    Log("tick change 3nd row")
    Dim ctp1 As CheckboxTreeItem = Sender
    Log(ctp1.Text & ": " & Ticked)
End Sub


Sub TI_SelectedItemChanged(SelectedItem As CheckboxTreeItem)
    Log("selected change ")
  
End Sub

Sub TI1_SelectedItemChanged(SelectedItem As CheckboxTreeItem)
    Log("selected wrong ")
  
End Sub
  
Sub CTI1_SelectedItemChanged(SelectedItem As CheckboxTreeItem)
    Log("selected change 2nd row")
  
End Sub
  
Sub CTP1_SelectedItemChanged(SelectedItem As CheckboxTreeItem)
    Log("selected change 3nd row")
  
End Sub
 
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
Seems like my code is so bad , or nobody knows how to solve it or the last option , there is a better way.

Please understand I am still a novice at B4J but I am loving it !!!
 
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Ey peter

Let me help you with your problem bit by bit. Attached you will find an example on how to create (no matter how many levels) the whole structure of a folder

right now is only showing folders:

upload_2017-4-24_15-26-3.png
 

Attachments

  • Example.zip
    19.1 KB · Views: 391
Last edited:
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
Ey peter

Let me help you with your problem bit by bit. Attached you will find an example on how to create (no matter how many levels) the whole structure of a folder

right now is only showing folders:

View attachment 55048


Wow, thank you... you know I have found that most of the people on this board are so friendly and helpful. Seems like their passion is programming and it shows when helping people.

I learnt programming by example and thank you for your input.

I actually think that some of the inital code came from an example you posted on one of the boards asking how to stop duplicate entries when re-opening a tree.
 
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Ey Peter!
You are welcome!

I learnt programming by example and thank you for your input.

I learn by helping people with their problems! so when i stumble with that problem in a future development i already have the knowledge to solve it.

I made a mistake in the attached example, i was skipping one floor, i re attached the example and now it shows all the folders correctly.

In this version, it will show all the Folders and all the files, the difference is that all the folders will have at least one child, with the word empty (so you will know who is a folder by counting their childs)
 
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
Ey Peter!
You are welcome!



I learn by helping people with their problems! so when i stumble with that problem in a future development i already have the knowledge to solve it.

I made a mistake in the attached example, i was skipping one floor, i re attached the example and now it shows all the folders correctly.

In this version, it will show all the Folders and all the files, the difference is that all the folders will have at least one child, with the word empty (so you will know who is a folder by counting their childs)

Just one question, it tells me you have a newer version than 5.00 which i checked on the site was the latest and it did not run, it gave errors.




newversion.png


Any ideas ?

java.lang.RuntimeException: Object should first be initialized (List).

Thanks
 
Last edited:
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Anyway,

Now that we have the creation of the treeview, lets get every file position.

B4X:
Sub Button1_Action
    getAbsolutePath(TreeView1.root,"")
End Sub

private Sub getAbsolutePath(tr As TreeItem,path As String)
    For Each tr2 As TreeItem In tr.children
        If tr2.Children.Size = 0 And tr2.Text <> "{Empty}" Then
            Log(path&"\"&tr2.Text)
        Else
            If path = "" Then
                getAbsolutePath(tr2,tr2.Text)
            Else
                getAbsolutePath(tr2,path&"\"&tr2.Text)
            End If
        End If
    Next
End Sub
attached Example

Change this variable value:

B4X:
Dim mainFolder As String = "C:\SIERRA"
 

Attachments

  • Example.zip
    21.7 KB · Views: 369
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Last but not least:

Retrieve path of selected items.
B4X:
private Sub TreeView1_SelectedItemChanged(SelectedItem As TreeItem)
    If SelectedItem.Children.Size > 0 Then
        Return 
    End If
   
    Dim path As String = getAbsolutePath2(SelectedItem)
    Log(path)
End Sub

private Sub getAbsolutePath2(tr As TreeItem) As String
    If tr.Parent.Root Then
        Return tr.Text
    Else
        Dim path As String = getAbsolutePath2(tr.parent)
        Return path&"\"&tr.text
    End If
End Sub
 
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
I have changed it so that I can do checkbox Directories , which is what I need to do.

Do you think there is any way to automatically check / uncheck all the Children if you Check a Parent ?

Here is what I have been playing with.

My next stage is to build an sql Database of all the selected Directories , Once that is done and on my screen I am happy with all the selections, The continue button will go through each of the directories and Make a list of all files dependant on their extention.


I build a program like this in Clarion a few years ago but I want to make it more usable on multi platform. It is really a music sorter into different genre. What I did not do before which I want to do now is to exclude files that have been sorted before. So there will eventually be a large datadase of files that I have sorted.

At the moment I also have the True and false for if it is ticked so that i can also remove the database record if it is unchecked. My thought was to add the records as soon as they were checked as I can get the full path easily.

Here is the code updated with the checkboxes

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 400
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private TreeView1 As TreeView
    Private fPath As String
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    TreeView1.SetCheckBoxesMode
    Dim mainFolder As String = "I:\nmusic\music"
'    Dim subfolders As List = getFolders(mainFolder)
    Dim tr As CheckboxTreeItem
    tr.Initialize("tr",mainFolder)
    TreeView1.Root.Children.Add(tr)
   
    recursiveFolder(tr,mainFolder)

    MainForm.show
End Sub

Sub recursiveFolder(tr As CheckboxTreeItem,folder As String)
    Dim l As List = getFolders(folder)
   
    If l.Size = 0 Then
        Dim tr2 As CheckboxTreeItem
        tr2.Initialize("","{Empty}")
        tr.Children.Add(tr2)
        Return
    End If

    For i = 0 To l.Size - 1
        Dim tr2 As CheckboxTreeItem
        tr2.Initialize("tr",l.Get(i))
        tr.Children.Add(tr2)
        If File.IsDirectory(folder,l.Get(i)) Then
            recursiveFolder(tr2,folder&"\"&l.Get(i))
       
        End If
        fPath=(folder&"\"&l.Get(i))
    Next
End Sub

private Sub getFolders(folder As String) As List
    Dim l As List = File.ListFiles(folder)   
    Dim lreturn As List
    lreturn.Initialize
    For i = 0 To l.size - 1
        Dim fName As String = l.Get(i)
        If File.IsDirectory(folder,fName) Then ' just directories
           'Log(fName)
           Log("path getfolders "&fPath)'&"\"&fName)
            lreturn.Add(fName)
        End If                                ' just directories
    Next

    Return lreturn
End Sub
Sub TR_CheckedChange(Ticked As Boolean)
    If Ticked = True Then
        'Log("tick change")
        Dim ti As CheckboxTreeItem = Sender
        'Log(ti.Text & ": " & Ticked)
        'Log("Path= "&fPath)
        Dim path As String = getAbsolutePath2(ti)
       
        Log("tick status "& Ticked)
        Log(path)
    Else
        Dim ti As CheckboxTreeItem = Sender
        'folders.Clear
        Log("tick status "& Ticked)
        Dim path As String = getAbsolutePath2(ti)
        Log(path)
    End If
End Sub

private Sub TreeView1_SelectedItemChanged(SelectedItem As TreeItem)
    If SelectedItem.Children.Size > 0 Then
        Return
    End If
  
    Dim path As String = getAbsolutePath2(SelectedItem)
    Log(path)
End Sub

private Sub getAbsolutePath2(tr As CheckboxTreeItem) As String
    If tr.Parent.Root Then
        Return tr.Text
    Else
        Dim path As String = getAbsolutePath2(tr.parent)
        Return path&"\"&tr.text
    End If
End Sub
 
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Ey Peter.

For your first question.

Do you think there is any way to automatically check / uncheck all the Children if you Check a Parent ?

B4X:
Sub TR_CheckedChange(Ticked As Boolean)
    Dim ti As CheckboxTreeItem = Sender
   
    If Ticked = True Then
        'Log("tick change")
        'Log(ti.Text & ": " & Ticked)
        'Log("Path= "&fPath)
        Dim path As String = getAbsolutePath2(ti)
       
        Log("tick status "& Ticked)
        Log(path)
    Else
        'folders.Clear
        Log("tick status "& Ticked)
        Dim path As String = getAbsolutePath2(ti)
        Log(path)
    End If

'New part. 
    For i = 0 To ti.Children.Size -1
        Dim ti2 As CheckboxTreeItem = ti.Children.Get(i)
        ti2.Checked = Ticked   
    Next
End Sub
 
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
At the moment I also have the True and false for if it is ticked so that i can also remove the database record if it is unchecked. My thought was to add the records as soon as they were checked as I can get the full path easily.

This will depend where the database is, if it is locally then its the best solution. If it is not, it would be better to accumulate them and send the queries at the end.
 
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
Oh then its easy,

You can even create the database on the fly!

After a long time, i have now learnt how to integrate sql into the program.

Now when you start it will make a data directory if there is none then it will drop the table so that you start clean

After that it will add to the table and delete from the table when you check or uncheck it.

Here is the project. There might be better ways to do this but it is my first attempt with sqlite.
 

Attachments

  • DirTreeWithCheckBoxSQL2.zip
    3.4 KB · Views: 333
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Looks Awesome!

Just some small comments:

This line is already calling DB.CreateDataBase
B4X:
    DB.InitDb
That means that you are running twice DB.CreateDataBase (appStart)

you may not need this:
B4X:
    If File.Exists(File.DirApp, "data/DJ.db") = False Then

'Because this one will create the database if it is not there:
'True means create
sql1.InitializeSQLite(File.DirApp, "data/DJ.db", True)

This line causes an error (that you are catching):
B4X:
    sql1.ExecNonQuery("DROP Table DirSelected" )

'To avoid the error and for the pourpose of better debugging you can use.
    sql1.ExecNonQuery("DROP Table IF EXISTS DirSelected" )

and last:
B4X:
sql1.ExecQuerySingleResult("SELECT count(*) FROM DirSelected WHERE dPath ="&Chr(34) & pTable & Chr(34))
sql1.ExecNonQuery("UPDATE `DirSelected` SET " & "iDone" & "='" & Newidone & "' WHERE `dPath` LIKE '" & SearchPath & "'")
Its always better to use the second options for this functions: ExecQuerySingleResult2 and ExecNonQuery2. Because escaping values will cause you problems in the long run.
 
Upvote 0

Peter Lewis

Active Member
Licensed User
Longtime User
Looks Awesome!

Just some small comments:

This line is already calling DB.CreateDataBase
B4X:
    DB.InitDb
That means that you are running twice DB.CreateDataBase (appStart)

you may not need this:
B4X:
    If File.Exists(File.DirApp, "data/DJ.db") = False Then

'Because this one will create the database if it is not there:
'True means create
sql1.InitializeSQLite(File.DirApp, "data/DJ.db", True)

This line causes an error (that you are catching):
B4X:
    sql1.ExecNonQuery("DROP Table DirSelected" )

'To avoid the error and for the pourpose of better debugging you can use.
    sql1.ExecNonQuery("DROP Table IF EXISTS DirSelected" )

and last:
B4X:
sql1.ExecQuerySingleResult("SELECT count(*) FROM DirSelected WHERE dPath ="&Chr(34) & pTable & Chr(34))
sql1.ExecNonQuery("UPDATE `DirSelected` SET " & "iDone" & "='" & Newidone & "' WHERE `dPath` LIKE '" & SearchPath & "'")
Its always better to use the second options for this functions: ExecQuerySingleResult2 and ExecNonQuery2. Because escaping values will cause you problems in the long run.


Hi

I did the first 2 with no problem, the last one I just cannot get right.

This is the error I am getting

java.sql.SQLException: [SQLITE_ERROR] SQL error or missing database (near "VALUES": syntax error)

B4X:
sql1.ExecNonQuery2("DELETE FROM DirSelected VALUES (?)", Array As Object(pTable)) ' delete field
 
Upvote 0

EnriqueGonzalez

Well-Known Member
Licensed User
Longtime User
Like this:

I suppose you refer to this function:
B4X:
Sub DeleteEntry(pTable As String)
            sql1.ExecNonQuery2("DELETE FROM  DirSelected WHERE dPath = ?",array as string(ptable))
End Sub

You use VALUES when you use an insert statement
INSERTO INTO table (column0,column1) VALUES (1,2)

DELETE FROM only requieres a WHERE clause
 
Upvote 0
Top