Imports System.IO
Imports System.Xml.Serialization
Imports System.Diagnostics

Public Class FrmWorld
    Private dragBoxFromMouseDown As Rectangle ' Variable to track the drag rectangle
    Private currentFileName As String = ""
    Private isModified As Boolean = False

#Region " Initiate "
    Private Sub FrmWorld_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        PopulateTreeView("D:\01 - Grags\Visual Studio\Bizarria New\Data\Decor")
        ' Ensure PicCanvas can accept drops (if not already set in MapCanvas class)
        PicCanvas.AllowDrop = True

        ' Add event handler for grid size selection
        AddHandler CboDimensions.SelectedIndexChanged, AddressOf CboDimensions_SelectedIndexChanged

        ' Set default grid size
        CboDimensions.SelectedItem = "32 x 32"

        ' Add "10000 x 10000" to the combo box if not already present
        Dim defaultMapSize As String = "15000 x 10000"

        If Not CboMapSize.Items.Contains(defaultMapSize) Then
            Dim unused = CboMapSize.Items.Add(defaultMapSize)
        End If

        ' Set default map size
        CboMapSize.SelectedItem = defaultMapSize

        ' Handle CanvasModified event
        AddHandler PicCanvas.CanvasModified, AddressOf CanvasModified_Handler

        LoadTreeViewState()
    End Sub
    Private Sub LoadTreeViewState()
        If File.Exists("TreeViewState.xml") Then
            Dim serializer As New XmlSerializer(GetType(TreeViewState))
            Using reader As New StreamReader("TreeViewState.xml")
                Dim state As TreeViewState = CType(serializer.Deserialize(reader), TreeViewState)

                ' Restore expanded nodes
                For Each nodePath As String In state.ExpandedNodes
                    Dim node As TreeNode = FindNodeByPath(TrvFiles.Nodes, nodePath)
                    node?.Expand()
                Next

                ' Restore selected node
                If Not String.IsNullOrEmpty(state.SelectedNodePath) Then
                    Dim selectedNode As TreeNode = FindNodeByPath(TrvFiles.Nodes, state.SelectedNodePath)
                    If selectedNode IsNot Nothing Then
                        TrvFiles.SelectedNode = selectedNode
                        selectedNode.EnsureVisible()
                    End If
                End If
            End Using
        End If
    End Sub
    Private Function FindNodeByPath(nodes As TreeNodeCollection, path As String) As TreeNode
        Dim parts() As String = path.Split("\"c)
        Return FindNodeByPathRecursive(nodes, parts, 0)
    End Function

    Private Function FindNodeByPathRecursive(nodes As TreeNodeCollection, parts() As String, index As Integer) As TreeNode
        For Each node As TreeNode In nodes
            If node.Text = parts(index) Then
                Return If(index = parts.Length - 1, node, FindNodeByPathRecursive(node.Nodes, parts, index + 1))
            End If
        Next
        Return Nothing
    End Function

#End Region

#Region " Populate TreeView and FlowPanel "
    Private Async Function LoadImagesAsync(directoryInfo As DirectoryInfo) As Task
        Dim imageExtensions() As String = {".jpg", ".jpeg", ".png", ".bmp", ".gif"}
        Dim imageFiles As New List(Of FileInfo)()

        For Each extension As String In imageExtensions
            imageFiles.AddRange(directoryInfo.GetFiles($"*{extension}", SearchOption.TopDirectoryOnly))
        Next

        For Each imageFile As FileInfo In imageFiles
            Await Task.Run(Sub()
                               ' Create an instance of the ImageItem control
                               Dim imageItem As New ImageItem() With {
                                .Image = Image.FromFile(imageFile.FullName),
                                .Filename = imageFile.Name, ' Display only the file name, not the full path
                                .FilePath = imageFile.FullName,
                                .Width = 100, ' Adjust as needed
                                .Height = 120 ' Adjust to accommodate image and label
                            }
                               ' Add event handlers for drag initiation
                               AddHandler imageItem.ImageItemMouseDownEvent, AddressOf ImageItem_MouseDown
                               AddHandler imageItem.ImageItemMouseMoveEvent, AddressOf ImageItem_MouseMove
                               AddHandler imageItem.ImageItemMouseUpEvent, AddressOf ImageItem_MouseUp
                               FLPImages.Invoke(Sub() FLPImages.Controls.Add(imageItem))
                           End Sub)
        Next
    End Function
    Private Sub PopulateTreeView(rootDirectory As String)
        TrvFiles.Nodes.Clear()
        Dim rootDirectoryInfo As New DirectoryInfo(rootDirectory)
        Dim rootNode As New TreeNode(rootDirectoryInfo.Name) With {.Tag = rootDirectoryInfo}
        Dim unused = TrvFiles.Nodes.Add(rootNode)
        AddDirectories(rootDirectoryInfo, rootNode)
    End Sub

    Private Sub AddDirectories(directoryInfo As DirectoryInfo, node As TreeNode)
        For Each directory As DirectoryInfo In directoryInfo.GetDirectories()
            Dim directoryNode As New TreeNode(directory.Name) With {.Tag = directory}
            Dim unused = node.Nodes.Add(directoryNode)
            AddDirectories(directory, directoryNode)
        Next
    End Sub

    Private Async Sub TrvFiles_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles TrvFiles.AfterSelect
        FLPImages.Controls.Clear()
        Dim selectedNode As TreeNode = e.Node
        If TypeOf selectedNode.Tag Is DirectoryInfo Then
            Dim directoryInfo As DirectoryInfo = CType(selectedNode.Tag, DirectoryInfo)
            Await LoadImagesAsync(directoryInfo)
        End If
    End Sub

#End Region

#Region " Mouse Events for Dragging Images "

    ' MouseDown event handler for ImageItem in FLPImages
    Private Sub ImageItem_MouseDown(sender As Object, e As MouseEventArgs)
        If e.Button = MouseButtons.Left Then
            ' Remember the point where the mouse down occurred
            Dim dragSize As Size = SystemInformation.DragSize
            ' Create a rectangle representing the area the mouse can move before a drag starts
            dragBoxFromMouseDown = New Rectangle(New Point(e.X - (dragSize.Width \ 2),
                                                       e.Y - (dragSize.Height \ 2)),
                                             dragSize)
        Else
            ' Reset drag rectangle
            dragBoxFromMouseDown = Rectangle.Empty
        End If
    End Sub

    ' MouseMove event handler for ImageItem in FLPImages
    Private Sub ImageItem_MouseMove(sender As Object, e As MouseEventArgs)
        If (e.Button And MouseButtons.Left) = MouseButtons.Left Then
            ' Check if the mouse has moved outside the drag rectangle
            If dragBoxFromMouseDown <> Rectangle.Empty AndAlso Not dragBoxFromMouseDown.Contains(e.Location) Then
                Dim imageItem As ImageItem = CType(sender, ImageItem)
                ' Get the image from the ImageItem
                Dim image As Image = imageItem.Image
                If image IsNot Nothing Then
                    ' Set custom cursor
                    Dim cursor As Cursor = CreateCursorFromImage(image)
                    Cursor.Current = cursor

                    ' Use standard data format to pass the image and file path
                    Dim dataObj As New DataObject()
                    dataObj.SetData(GetType(Bitmap), image) ' Set the image data with the Bitmap type
                    Dim imageFilePath As String = imageItem.FilePath
                    dataObj.SetData("ImageFilePath", imageFilePath) ' Set the image file path

                    ' Start the drag-and-drop operation
                    imageItem.DoDragDrop(dataObj, DragDropEffects.Copy)

                    ' Reset cursor after dragging
                    Cursor.Current = Cursors.Default
                End If
                ' Reset drag rectangle
                dragBoxFromMouseDown = Rectangle.Empty
            End If
        End If
    End Sub

    ' MouseUp event handler for ImageItem in FLPImages
    Private Sub ImageItem_MouseUp(sender As Object, e As MouseEventArgs)
        ' Reset drag rectangle
        dragBoxFromMouseDown = Rectangle.Empty
    End Sub

    ' Helper function to convert Image to Cursor
    Private Function CreateCursorFromImage(image As Image) As Cursor
        Dim bitmap As New Bitmap(image)
        Dim iconHandle As IntPtr = bitmap.GetHicon()
        Return New Cursor(iconHandle)
    End Function

    ' Event handler for CanvasModified event
    Private Sub CanvasModified_Handler()
        isModified = True
    End Sub
#End Region

#Region " CTMTrvFIles "
    ' Event handler for MnuCollapse Click
    Private Sub MnuCollapse_Click(sender As Object, e As EventArgs) Handles MnuCollapse.Click
        Dim selectedNode As TreeNode = TrvFiles.SelectedNode
        If selectedNode IsNot Nothing Then
            CollapseAllNodes(selectedNode)
        Else
            MessageBox.Show("Please select a node to collapse.", "Collapse Node", MessageBoxButtons.OK, MessageBoxIcon.Information)
        End If
    End Sub

    ' Event handler for MnuExpand Click
    Private Sub MnuExpand_Click(sender As Object, e As EventArgs) Handles MnuExpand.Click
        Dim selectedNode As TreeNode = TrvFiles.SelectedNode
        If selectedNode IsNot Nothing Then
            ExpandAllNodes(selectedNode)
        Else
            MessageBox.Show("Please select a node to expand.", "Expand Node", MessageBoxButtons.OK, MessageBoxIcon.Information)
        End If
    End Sub
    ' Recursive method to expand all child nodes of a given node
    Private Sub ExpandAllNodes(node As TreeNode)
        node.Expand()
        For Each childNode As TreeNode In node.Nodes
            ExpandAllNodes(childNode)
        Next
    End Sub

    ' Recursive method to collapse all child nodes of a given node
    Private Sub CollapseAllNodes(node As TreeNode)
        For Each childNode As TreeNode In node.Nodes
            CollapseAllNodes(childNode)
        Next
        node.Collapse()
    End Sub
#End Region

#Region " Zoom "
    Private Sub PicCanvas_ZoomLevelChanged(zoomLevel As Single) Handles PicCanvas.ZoomLevelChanged
        ' Update the lblZoom label
        lblZoom.Text = "Zoom: " & (zoomLevel * 100).ToString("0.##") & "%"
    End Sub
#End Region

#Region " ToolStrip "
    ' Click event handler for the "Zoom to Fit" button
    Private Sub BtnZoomToFit_Click(sender As Object, e As EventArgs) Handles BtnZoomToFit.Click
        PicCanvas.ZoomToFit()
    End Sub

    Private Sub CboDimensions_SelectedIndexChanged(sender As Object, e As EventArgs)
        Dim selectedText As String = CboDimensions.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim size As Integer
            If Integer.TryParse(dimensions(0).Trim(), size) Then
                ' Update grid size in PicCanvas
                PicCanvas.GridSize = size
                ' Optionally, update existing tiles to snap to new grid
                PicCanvas.SnapAllTilesToGrid()
            End If
        End If
    End Sub

    Private Sub CboMapSize_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CboMapSize.SelectedIndexChanged
        Dim selectedText As String = CboMapSize.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim width As Integer
            Dim height As Integer
            If Integer.TryParse(dimensions(0).Trim(), width) AndAlso Integer.TryParse(dimensions(1).Trim(), height) Then
                ' Update map size
                PicCanvas.SetMapSize(width, height)
            End If
        End If
    End Sub

#End Region

#Region " Save and Load Functionality "

    <Serializable>
    Public Class CanvasData
        Public Property MapTiles As List(Of MapTileData)
        Public Property GridSize As Integer
        Public Property MapSize As Size
    End Class

    <Serializable>
    Public Class MapTileData
        Public Property ImageFilePath As String
        Public Property Position As Point
        Public Property Size As Size
    End Class

    Private Sub SaveCanvas(fileName As String)
        ' Create CanvasData object
        Dim canvasData As New CanvasData With {
            .GridSize = PicCanvas.GridSize,
            .MapSize = PicCanvas.AutoScrollMinSize,
            .MapTiles = New List(Of MapTileData)()
        }

        ' Map tiles
        For Each tile As MapTile In PicCanvas.Tiles
            Dim tileData As New MapTileData With {
                .ImageFilePath = tile.ImageFilePath,
                .Position = tile.Position,
                .Size = tile.Size
            }
            canvasData.MapTiles.Add(tileData)
        Next

        ' Serialize to XML
        Dim serializer As New XmlSerializer(GetType(CanvasData))

        Using writer As New StreamWriter(fileName)
            serializer.Serialize(writer, canvasData)
        End Using

        isModified = False
    End Sub

    Private Sub LoadCanvas(fileName As String)
        ' Deserialize from XML
        Dim serializer As New XmlSerializer(GetType(CanvasData))
        Using reader As New StreamReader(fileName)
            Dim canvasData As CanvasData = CType(serializer.Deserialize(reader), CanvasData)

            ' Clear existing tiles
            PicCanvas.ClearTiles()

            ' Set grid size and map size
            PicCanvas.GridSize = canvasData.GridSize
            PicCanvas.SetMapSize(canvasData.MapSize.Width, canvasData.MapSize.Height)

            ' Load tiles
            For Each tileData As MapTileData In canvasData.MapTiles
                ' Load image from file path
                If File.Exists(tileData.ImageFilePath) Then
                    Dim image As Image = Image.FromFile(tileData.ImageFilePath)
                    Dim tileImage As New Bitmap(image, tileData.Size)

                    Dim mapTile As New MapTile With {
                        .Image = tileImage,
                        .ImageFilePath = tileData.ImageFilePath,
                        .Position = tileData.Position,
                        .Size = tileData.Size
                    }

                    PicCanvas.AddTile(mapTile)
                Else
                    ' Handle missing image file
                    Dim unused = MessageBox.Show("Image file not found: " & tileData.ImageFilePath)
                End If
            Next
        End Using

        isModified = False
    End Sub

    Private Sub MnuNew_Click(sender As Object, e As EventArgs) Handles MnuNew.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If

        PicCanvas.ClearTiles()
        currentFileName = ""
        isModified = False
    End Sub

    Private Sub MnuSave_Click(sender As Object, e As EventArgs) Handles MnuSave.Click
        If String.IsNullOrEmpty(currentFileName) Then
            MnuSaveAs_Click(sender, e)
        Else
            SaveCanvas(currentFileName)
        End If
    End Sub

    Private Sub MnuSaveAs_Click(sender As Object, e As EventArgs) Handles MnuSaveAs.Click
        Using sfd As New SaveFileDialog()
            sfd.Filter = "Map Files (*.map)|*.map|All Files (*.*)|*.*"
            If sfd.ShowDialog() = DialogResult.OK Then
                currentFileName = sfd.FileName
                SaveCanvas(currentFileName)
            End If
        End Using
    End Sub

    Private Sub MnuLoad_Click(sender As Object, e As EventArgs) Handles MnuLoad.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If

        Using ofd As New OpenFileDialog()
            ofd.Filter = "Map Files (*.map)|*.map|All Files (*.*)|*.*"
            If ofd.ShowDialog() = DialogResult.OK Then
                currentFileName = ofd.FileName
                LoadCanvas(currentFileName)
            End If
        End Using
    End Sub

    Private Sub MnuExit_Click(sender As Object, e As EventArgs) Handles MnuExit.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If
        Close()
    End Sub

#End Region

#Region " Closing "
    Private Sub FrmWorld_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        ' Save TreeView state
        SaveTreeViewState()
    End Sub
    Private Sub SaveTreeViewState()
        Dim state As New TreeViewState()

        ' Collect expanded nodes
        For Each node As TreeNode In TrvFiles.Nodes
            SaveExpandedNodes(node, state)
        Next

        ' Save selected node
        If TrvFiles.SelectedNode IsNot Nothing Then
            state.SelectedNodePath = TrvFiles.SelectedNode.FullPath
        End If

        ' Serialize to XML
        Dim serializer As New XmlSerializer(GetType(TreeViewState))
        Using writer As New StreamWriter("TreeViewState.xml")
            serializer.Serialize(writer, state)
        End Using
    End Sub

    Private Sub SaveExpandedNodes(node As TreeNode, state As TreeViewState)
        If node.IsExpanded Then
            state.ExpandedNodes.Add(node.FullPath)
        End If
        For Each childNode As TreeNode In node.Nodes
            SaveExpandedNodes(childNode, state)
        Next
    End Sub
#End Region

#Region " Zoom "

    Private Sub Mnu50_Click(sender As Object, e As EventArgs) Handles Mnu50.Click
        SetZoom(0.5F)
    End Sub

    Private Sub Mnu100_Click(sender As Object, e As EventArgs) Handles Mnu100.Click
        SetZoom(1.0F)
    End Sub

    Private Sub Mnu250_Click(sender As Object, e As EventArgs) Handles Mnu250.Click
        SetZoom(2.5F)
    End Sub

    Private Sub Mnu500_Click(sender As Object, e As EventArgs) Handles Mnu500.Click
        SetZoom(5.0F)
    End Sub

    Private Sub Mnu1000_Click(sender As Object, e As EventArgs) Handles Mnu1000.Click
        SetZoom(10.0F)
    End Sub

    Private Sub SetZoom(ByVal zoomLevel As Single)
        ' Set the zoom level on PicCanvas
        PicCanvas.ZoomLevel = zoomLevel
    End Sub
    Private Sub TrvFiles_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles TrvFiles.NodeMouseClick
        If e.Button = MouseButtons.Right Then
            ' Select the node under the mouse cursor
            TrvFiles.SelectedNode = e.Node
        End If
    End Sub
    Private Sub MnuOpenInExplorer_Click(sender As Object, e As EventArgs) Handles MnuOpenInExplorer.Click
        Dim selectedNode As TreeNode = TrvFiles.SelectedNode
        If selectedNode IsNot Nothing AndAlso TypeOf selectedNode.Tag Is DirectoryInfo Then
            Dim directoryInfo As DirectoryInfo = CType(selectedNode.Tag, DirectoryInfo)
            If directoryInfo.Exists Then
                Try
                    ' Open the folder in Windows Explorer
                    Process.Start("explorer.exe", directoryInfo.FullName)
                Catch ex As Exception
                    MessageBox.Show("Failed to open folder: " & ex.Message)
                End Try
            Else
                MessageBox.Show("Directory does not exist.")
            End If
        Else
            MessageBox.Show("No directory selected.")
        End If
    End Sub

    Private Sub PicCanvas_MouseMove(sender As Object, e As MouseEventArgs) Handles PicCanvas.MouseMove
        ' Convert the mouse position to canvas coordinates
        Dim canvasPoint As Point = PicCanvas.ClientToCanvas(e.Location)
        ' Update the status strip label
        STBMouseCoords.Text = $"X: {canvasPoint.X}, Y: {canvasPoint.Y}"
    End Sub
#End Region
End Class


I have added a comboBox (CboSearch). The ComboBox is at the top of TrvFiles. The idea is, I want the user to be able to search for Images. The search will start from the selected to node and search all Children and their children etc for Chosen Search. 

The reason I've done it in the form of a combobox is I want to save the search. So it remembers up to 10 searches. These searches must be saved to file and also ordered by last used. For example. 

We search Wood, Trees, Box. The comboBox will look like this

Wood
Trees
Box

Now we seach Trees.. the comboBox will look like this...

Trees
Wood
Box