<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Search &#8211; Xojo Programming Blog</title>
	<atom:link href="https://blog.xojo.com/tag/search/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.xojo.com</link>
	<description>Blog about the Xojo programming language and IDE</description>
	<lastBuildDate>Wed, 24 Jun 2026 21:40:10 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.5</generator>
	<item>
		<title>Build a Threaded Find in Files Solution</title>
		<link>https://blog.xojo.com/2026/06/25/build-a-threaded-find-in-files-solution/</link>
		
		<dc:creator><![CDATA[Gabriel Ludosanu]]></dc:creator>
		<pubDate>Thu, 25 Jun 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[Cross-Platform]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Files]]></category>
		<category><![CDATA[Preemptive Threads]]></category>
		<category><![CDATA[Search]]></category>
		<category><![CDATA[Threads]]></category>
		<guid isPermaLink="false">https://blog.xojo.com/?p=16269</guid>

					<description><![CDATA[In our previous posts, we built a utility to find text inside files within a single folder, and then added recursion to crawl subfolders. But if you&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In our previous posts, we built a utility to <a href="https://blog.xojo.com/2026/04/02/build-a-simple-find-in-files-function/" target="_blank" rel="noreferrer noopener">find text inside files within a single folder</a>, and then <a href="https://blog.xojo.com/2026/05/04/build-a-recursive-find-in-files-function/" target="_blank" rel="noreferrer noopener">added recursion to crawl subfolders</a>. But if you ran that code on a large directory, like your entire Documents folder, you probably noticed two big problems:</p>



<ol class="wp-block-list">
<li>The UI froze completely until the search finished.</li>



<li>If the folder tree was deep enough, you might have hit a&nbsp;<code>StackOverflowException</code>.</li>



<li>We haven&#8217;t actually built a proper UI for it yet!</li>
</ol>



<p>Today, we&#8217;re fixing all three. We&#8217;ll introduce a custom app UI, create a&nbsp;<code>Thread</code>&nbsp;subclass to perform the search in the background, and swap our recursive folder crawl for a flatter, iterative approach to spare our stack memory. We&#8217;ll also add a file size limit to avoid choking on massive unreadable files.</p>



<h2 class="wp-block-heading" id="step-1-designing-the-ui">Step 1: Designing the UI</h2>



<p>First, let&#8217;s build the interface so you have a real application to run. On your main&nbsp;<code>Window1</code>&nbsp;(or whichever Window you are using), we need to add a few controls:</p>



<ul class="wp-block-list">
<li><strong>Path Selection</strong>: Add a&nbsp;<code>DesktopTextField</code>&nbsp;(named&nbsp;<code>fieldPath</code>, set to ReadOnly) to display the chosen folder path, and a&nbsp;<code>DesktopButton</code>&nbsp;(named&nbsp;<code>buttonSelectPath</code>, caption&nbsp;<code>...</code>) next to it to open a&nbsp;<code>SelectFolderDialog</code>.</li>



<li><strong>Search Input</strong>: Add a&nbsp;<code>DesktopSearchField</code>&nbsp;(named&nbsp;<code>fieldSearchTerm</code>) where the user types the text they want to find.</li>



<li><strong>Action Button</strong>: Add a&nbsp;<code>DesktopButton</code>&nbsp;(named&nbsp;<code>buttonSearchThreaded</code>, caption&nbsp;<code>Search Threaded</code>) to start and cancel the search.</li>



<li><strong>Results List</strong>: Add a&nbsp;<code>DesktopListBox</code>&nbsp;(named&nbsp;<code>listResults</code>) and set its Column Count to 3. (Optional: Set the InitialValue of the Column headers to&nbsp;<code>File | Line No. | Context</code>).</li>



<li><strong>Status labels</strong>: Add a&nbsp;<code>DesktopLabel</code>&nbsp;(named&nbsp;<code>labelStatusValue</code>) at the bottom of the window to display the live search progress.</li>
</ul>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="602" height="452" src="https://blog.xojo.com/wp-content/uploads/2026/05/threaded-find-in-files-xojo-solution.png" alt="" class="wp-image-16272" srcset="https://blog.xojo.com/wp-content/uploads/2026/05/threaded-find-in-files-xojo-solution.png 602w, https://blog.xojo.com/wp-content/uploads/2026/05/threaded-find-in-files-xojo-solution-300x225.png 300w" sizes="(max-width: 602px) 100vw, 602px" /></figure>
</div>


<h2 class="wp-block-heading" id="step-2-creating-the-thread-subclass">Step 2: Creating the Thread Subclass</h2>



<p>Threading lets us run our file search simultaneously with the main application, keeping our UI totally responsive.</p>



<p>Instead of writing standard methods, we need to create a dedicated Class.</p>



<ol class="wp-block-list">
<li>Add a&nbsp;<strong>New Class</strong>, in the <code>SearchUtils</code> module and name it&nbsp;<code>FileSearch</code>.</li>



<li>In the Inspector, change its&nbsp;<strong>Super</strong>&nbsp;to&nbsp;<code>Thread</code>.</li>



<li>Add the following Properties to your new class:
<ul class="wp-block-list">
<li><code>MaximumFileSizeMB As Integer = 1</code></li>



<li><code>SearchTerm As String</code></li>



<li><code>TargetFolder As FolderItem</code></li>



<li><code>startTime As Double</code>&nbsp;(Set Scope to Private)</li>
</ul>
</li>
</ol>



<h3 class="wp-block-heading" id="defining-custom-events">Defining Custom Events</h3>



<p>We need our Thread to broadcast events to the Window that&#8217;s holding it. Add these three Event Definitions to your&nbsp;<code>FileSearch</code>&nbsp;class:</p>



<ul class="wp-block-list">
<li><code>Complete(timeElapsed as Double)</code></li>



<li><code>MatchFound(result as SearchUtils.SearchHit)</code></li>



<li><code>StatusUpdate(currentPath as String)</code></li>
</ul>



<h2 class="wp-block-heading" id="step-3-the-iterative-approach-no-recursion">Step 3: The iterative approach</h2>



<p>Recursion is elegant, but crawling a hierarchy thousands of folders deep throws a&nbsp;<code>StackOverflowException</code>. Instead, we flatten the traversal using an array as a queue. We start by adding our root folder&#8217;s children to an array. As we iterate through that array, any folders we find get their children appended to the&nbsp;<em>end</em>&nbsp;of the same array.</p>



<p>Here is exactly how it looks in the Thread&#8217;s&nbsp;<code>Run</code>&nbsp;event. Add the&nbsp;<code>Run</code>&nbsp;event to your&nbsp;<code>FileSearch</code>&nbsp;class and use this exact code and notice the use of&nbsp;<code>AddUserInterfaceUpdate</code>&nbsp;to safely queue UI updates:</p>



<pre class="wp-block-code"><code>Sub Run() Handles Run
  Var maxFileSize As Double = If( Self.MaximumFileSizeMB &lt; 1, 999999999 * 1024 * 1024, Self.MaximumFileSizeMB * 1024 * 1024 )
  
  Var result As Dictionary
  
  Var lastUpdate As Integer = System.Microseconds
  
  If Not (targetFolder Is Nil) And targetFolder.Exists And Not searchTerm.IsEmpty Then
    
    Var child As FolderItem
    Var toSearch() As FolderItem
    For Each child In targetFolder.Children
      toSearch.Add( child )
    Next
    
    Var ti As TextInputStream
    Var line As String
    Var lineNumber As Integer = -1
    
    For index As Integer = 0 To toSearch.LastIndex
      child = toSearch(index)
      
      If System.Microseconds - lastUpdate &gt; 1000000 Then
        result = New Dictionary
        result.Value( "type" ) = "status"
        result.Value( "current" ) = child.NativePath
        AddUserInterfaceUpdate( result )
        lastUpdate = System.Microseconds
      End If
      
      If child.IsFolder Then
        Try
          
          #Pragma BreakOnExceptions False
          
          For Each innerChild As FolderItem In child.Children
            toSearch.Add( innerChild )
          Next
          
          #Pragma BreakOnExceptions Default
          
        Catch e As IOException
          '// Skip inaccessible folders
        End Try
      Else
        If child.Length &lt; maxFileSize Then
          Try
            ti = TextInputStream.Open( child )
            ti.Encoding = Encodings.UTF8
            
            lineNumber = 0
            
            While Not ti.EndOfFile
              lineNumber = lineNumber + 1
              line = ti.ReadLine
              If line.Contains( searchTerm, ComparisonOptions.CaseInsensitive ) Then
                result = New Dictionary
                result.Value( "type" ) = "match"
                result.Value( "path" ) = child.NativePath
                result.Value( "lineNo" ) = lineNumber
                result.Value( "lineText" ) = line
                AddUserInterfaceUpdate( result )
              End If
            Wend
          Catch ioe As IOException
            '// Skip unreadable files
          End Try
        End If
      End If
    Next
  End If
  
  result = New Dictionary( New Pair( "type", "complete" ) )
  AddUserInterfaceUpdate( result )
End Sub</code></pre>



<h2 class="wp-block-heading" id="step-4-feeding-updates-to-the-ui-safely">Step 4: Sending Updates to the UI safely</h2>



<p>In a thread, you&nbsp;<strong>cannot</strong>&nbsp;update UI controls directly. That&#8217;s why we used&nbsp;<code>AddUserInterfaceUpdate</code>&nbsp;above passing a&nbsp;<code>Dictionary</code>&nbsp;of data. This triggers the Thread&#8217;s&nbsp;<code>UserInterfaceUpdate</code>&nbsp;event on the main UI thread.</p>



<p>Add the&nbsp;<code>UserInterfaceUpdate</code>&nbsp;event to your&nbsp;<code>FileSearch</code>&nbsp;class:</p>



<pre class="wp-block-code"><code>Sub UserInterfaceUpdate(data() As Dictionary) Handles UserInterfaceUpdate
  For Each d As Dictionary In data
    Select Case d.Value( "type" )
    Case "match" 
      Var hit As New SearchHit( d.Value( "path" ), d.Value( "lineNo" ), d.Value( "lineText" ) )
      RaiseEvent MatchFound( hit )
    Case "complete"
      RaiseEvent Complete( (System.Microseconds - startTime) / 1000 )
    Case "status"
      RaiseEvent StatusUpdate( d.Value( "current" ) )
    End Select
  Next
End Sub</code></pre>



<p>And finally, add a public method called&nbsp;<code>Search</code>&nbsp;to the class, making sure it executes Preemptively across CPU cores:</p>



<pre class="wp-block-code"><code>Public Sub Search(path As FolderItem, term As String)
  Self.Type = Thread.Types.Preemptive
  
  startTime = System.Microseconds
  
  Self.TargetFolder = path
  Self.SearchTerm = term
  Self.Start
End Sub</code></pre>



<h2 class="wp-block-heading" id="step-5-wiring-the-ui">Step 5: Wiring the UI</h2>



<p>Now, let&#8217;s bring it all together. Select&nbsp;<code>Window1</code>&nbsp;and&nbsp;<strong>simply drag the&nbsp;<code>FileSearch</code>&nbsp;class from your Navigator onto your Window.</strong>&nbsp;It will appear in a shelf at the bottom as&nbsp;<code>FileSearch1</code>&nbsp;(rename it to&nbsp;<code>seeker</code>).</p>



<p>Implement the Event Handlers to your new&nbsp;<code>seeker</code>&nbsp;control:</p>



<p><strong>seeker.Complete:</strong></p>



<pre class="wp-block-code"><code>Sub Complete(timeElapsed As Double) Handles Complete
  buttonSelectPath.Enabled = True
  buttonSearchThreaded.Caption = "Search Threaded"
  fieldSearchTerm.Enabled = True

  Var count As Integer = listResults.LastRowIndex + 1
  Var timeElapsedSeconds As Double = timeElapsed / 1000
  labelStatusValue.Text = "Complete in " + timeElapsedSeconds.ToString( "#0.00" ) + " seconds! " + count.ToString( "#" ) + " results found."
End Sub</code></pre>



<p><strong>seeker.MatchFound:</strong></p>



<pre class="wp-block-code"><code>Sub MatchFound(result As SearchUtils.SearchHit) Handles MatchFound
  listResults.AddRow( result.FilePath, result.LineNumber.ToString( "#" ), result.LineText )
  listResults.RowTagAt( listResults.LastAddedRowIndex ) = result
End Sub</code></pre>



<p><strong>seeker.StatusUpdate:</strong></p>



<pre class="wp-block-code"><code>Sub StatusUpdate(currentPath As String) Handles StatusUpdate
  labelStatusValue.Text = "Searching '" + currentPath + "'"
End Sub</code></pre>



<p id="step-5-wiring-the-ui">And finally, the code in&nbsp;<code>buttonSearchThreaded</code>&nbsp;to kick off the search (and handle nice cancellation):</p>



<pre class="wp-block-code"><code>Sub Pressed() Handles Pressed
  Select Case Me.Caption
  Case "Search Threaded"
    If fieldSearchTerm.Text.Length &gt;= 3 Then
      If seeker.ThreadState = Thread.ThreadStates.NotRunning Then

        listResults.RemoveAllRows

        fieldSearchTerm.Enabled = False
        buttonSelectPath.Enabled = False
        labelStatusValue.Text = "Searching..."

        Me.Caption = "Cancel"

        seeker.Search( Self.path, fieldSearchTerm.Text )

      End If
    End If
  Case "Cancel"
    seeker.Stop
    labelStatusValue.Text = "Cancelled"
  End Select
End Sub</code></pre>



<h2 class="wp-block-heading" id="step-5-wiring-the-ui">Summary</h2>



<p id="step-5-wiring-the-ui">By switching from recursion to a linear queue, we bulletproofed the code against&nbsp;<code>StackOverflowExceptions</code>. By moving the heavy lifting to a preemptive&nbsp;<code>Thread</code>, we completely unlocked the UI, making the app feel incredibly fast and responsive even while chewing through thousands of files. And using&nbsp;<code>AddUserInterfaceUpdate</code>, we safely pipe results directly back to the user without locking up the UI!</p>



<p id="step-5-wiring-the-ui">You can download the project here: <a href="https://blog.xojo.com/wp-content/uploads/2026/05/SearchUtils.FindInFiles.zip">SearchUtils.FindInFiles.zip</a></p>



<h3 class="wp-block-heading" id="step-5-wiring-the-ui">Special Thanks</h3>



<p id="step-5-wiring-the-ui">A huge shoutout and special thanks to Xojo MVP and forum user&nbsp;<strong>Anthony G. Cyphers</strong>. Anthony generously provided the solution to evolve the&nbsp;<code>SearchUtils</code>&nbsp;module into this threaded version, along with the demo app UI.</p>



<p id="step-5-wiring-the-ui"><em>P.S. Got your own spin on this utility? Have ideas for new features or alternative approaches? Jump into the&nbsp;</em><a href="https://forum.xojo.com/c/code-sharing/62" data-type="link" data-id="https://forum.xojo.com/c/code-sharing/62" target="_blank" rel="noreferrer noopener">Xojo Code Sharing Forum</a><em>&nbsp; and share your improvements with the community!</em></p>



<p id="step-5-wiring-the-ui">Happy coding!</p>



<p><em>Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!</em></p>



<ul class="wp-block-social-links has-normal-icon-size is-content-justification-center is-layout-flex wp-container-core-social-links-is-layout-16018d1d wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-facebook  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.facebook.com/goxojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 2C6.5 2 2 6.5 2 12c0 5 3.7 9.1 8.4 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.3v7C18.3 21.1 22 17 22 12c0-5.5-4.5-10-10-10z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Facebook</span></a></li>

<li class="wp-social-link wp-social-link-x  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://x.com/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M13.982 10.622 20.54 3h-1.554l-5.693 6.618L8.745 3H3.5l6.876 10.007L3.5 21h1.554l6.012-6.989L15.868 21h5.245l-7.131-10.378Zm-2.128 2.474-.697-.997-5.543-7.93H8l4.474 6.4.697.996 5.815 8.318h-2.387l-4.745-6.787Z" /></svg><span class="wp-block-social-link-label screen-reader-text">X</span></a></li>

<li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.linkedin.com/company/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-github  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://github.com/topics/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12,2C6.477,2,2,6.477,2,12c0,4.419,2.865,8.166,6.839,9.489c0.5,0.09,0.682-0.218,0.682-0.484 c0-0.236-0.009-0.866-0.014-1.699c-2.782,0.602-3.369-1.34-3.369-1.34c-0.455-1.157-1.11-1.465-1.11-1.465 c-0.909-0.62,0.069-0.608,0.069-0.608c1.004,0.071,1.532,1.03,1.532,1.03c0.891,1.529,2.341,1.089,2.91,0.833 c0.091-0.647,0.349-1.086,0.635-1.337c-2.22-0.251-4.555-1.111-4.555-4.943c0-1.091,0.39-1.984,1.03-2.682 C6.546,8.54,6.202,7.524,6.746,6.148c0,0,0.84-0.269,2.75,1.025C10.295,6.95,11.15,6.84,12,6.836 c0.85,0.004,1.705,0.114,2.504,0.336c1.909-1.294,2.748-1.025,2.748-1.025c0.546,1.376,0.202,2.394,0.1,2.646 c0.64,0.699,1.026,1.591,1.026,2.682c0,3.841-2.337,4.687-4.565,4.935c0.359,0.307,0.679,0.917,0.679,1.852 c0,1.335-0.012,2.415-0.012,2.741c0,0.269,0.18,0.579,0.688,0.481C19.138,20.161,22,16.416,22,12C22,6.477,17.523,2,12,2z"></path></svg><span class="wp-block-social-link-label screen-reader-text">GitHub</span></a></li>

<li class="wp-social-link wp-social-link-youtube  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.youtube.com/c/XojoInc" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M21.8,8.001c0,0-0.195-1.378-0.795-1.985c-0.76-0.797-1.613-0.801-2.004-0.847c-2.799-0.202-6.997-0.202-6.997-0.202 h-0.009c0,0-4.198,0-6.997,0.202C4.608,5.216,3.756,5.22,2.995,6.016C2.395,6.623,2.2,8.001,2.2,8.001S2,9.62,2,11.238v1.517 c0,1.618,0.2,3.237,0.2,3.237s0.195,1.378,0.795,1.985c0.761,0.797,1.76,0.771,2.205,0.855c1.6,0.153,6.8,0.201,6.8,0.201 s4.203-0.006,7.001-0.209c0.391-0.047,1.243-0.051,2.004-0.847c0.6-0.607,0.795-1.985,0.795-1.985s0.2-1.618,0.2-3.237v-1.517 C22,9.62,21.8,8.001,21.8,8.001z M9.935,14.594l-0.001-5.62l5.404,2.82L9.935,14.594z"></path></svg><span class="wp-block-social-link-label screen-reader-text">YouTube</span></a></li></ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Build a Recursive Find in Files Function</title>
		<link>https://blog.xojo.com/2026/05/04/build-a-recursive-find-in-files-function/</link>
		
		<dc:creator><![CDATA[Gabriel Ludosanu]]></dc:creator>
		<pubDate>Mon, 04 May 2026 15:32:02 +0000</pubDate>
				<category><![CDATA[Cross-Platform]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Files]]></category>
		<category><![CDATA[Method Overload]]></category>
		<category><![CDATA[Recursiveness]]></category>
		<category><![CDATA[Search]]></category>
		<guid isPermaLink="false">https://blog.xojo.com/?p=16179</guid>

					<description><![CDATA[In the&#160;previous post, we built a utility to find text inside files within a single folder. Today we&#8217;re upgrading that utility to support recursive searching&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In the&nbsp;<a href="https://blog.xojo.com/2026/04/02/build-a-simple-find-in-files-function/">previous post</a>, we built a utility to find text inside files within a single folder. Today we&#8217;re upgrading that utility to support recursive searching with depth control. We&#8217;ll also refactor the code to keep it maintainable as it gets more complex.</p>



<h2 class="wp-block-heading" id="the-goal">The Goal</h2>



<p>We want to:</p>



<ol class="wp-block-list">
<li>Search through subfolders automatically.</li>



<li>Add a&nbsp;<code>maxDepth</code>&nbsp;parameter so we don&#8217;t accidentally crawl the entire hard drive.</li>



<li>Keep the simple version working using method overloading.</li>
</ol>



<h2 class="wp-block-heading" id="step-1-move-file-reading-to-its-own-method">Step 1: Move File Reading to its own Method</h2>



<p>The original&nbsp;<code>FindInFiles</code>&nbsp;function did everything: validated the folder, looped through children, read file content, and matched text. Adding recursion to that would make the code look ugly, so let&#8217;s refactor our code.</p>



<p>We&#8217;ll start by moving the file-reading logic into its own private method:&nbsp;<code>SearchFile</code>. This keeps the &#8220;search&#8221; logic separate from the &#8220;looping&#8221; logic.</p>



<pre class="wp-block-code"><code>Private Sub SearchFile(file As FolderItem, searchTerm As String, hits() As SearchHit)
  Try
    Var input As TextInputStream = TextInputStream.Open(file)
    input.Encoding = Encodings.UTF8
    Var lineNumber As Integer = 0
    While Not input.EndOfFile
      Var line As String = input.ReadLine
      lineNumber = lineNumber + 1
      If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
        hits.Add(New SearchHit(file.NativePath, lineNumber, line))
      End If
    Wend
    input.Close
  Catch error As IOException
    // Skip unreadable files
  End Try
End Sub</code></pre>



<h2 class="wp-block-heading" id="step-2-recursive-folder-traversal">Step 2: Recursive Folder Traversal</h2>



<p>Now we create&nbsp;<code>SearchFolder</code>. This method loops through a folder. If it finds a file, it calls&nbsp;<code>SearchFile</code>. If it finds a folder, it calls itself.</p>



<p>To keep this safe, we Use&nbsp;<code>maxDepth</code>:</p>



<ul class="wp-block-list">
<li><code>maxDepth = 0</code>: Search only this folder.</li>



<li><code>maxDepth &gt; 0</code>: Search this folder and N levels of subfolders.</li>



<li><code>maxDepth = -1</code>: Search everything (unlimited).</li>
</ul>



<pre class="wp-block-code"><code>Private Sub SearchFolder(folder As FolderItem, searchTerm As String, hits() As SearchHit, maxDepth As Integer)
   Try
    For Each item As FolderItem In folder.Children
      If item Is Nil Or Not item.Exists Or Not item.IsReadable Then Continue
      If item.IsFolder Then
        If maxDepth &lt;&gt; 0 Then
          Var nextDepth As Integer = If(maxDepth &gt; 0, maxDepth - 1, maxDepth)
          SearchFolder(item, searchTerm, hits, nextDepth)
        End If
      Else
        SearchFile(item, searchTerm, hits)
      End If
    Next
  Catch error As IOException
    // Skip inaccessible folders
  End Try
End Sub</code></pre>



<p>The logic in&nbsp;<code>nextDepth</code>&nbsp;lets us count down if there&#8217;s a limit, or stay at -1 if there isn&#8217;t.</p>



<h2 class="wp-block-heading" id="step-3-support-both-versions-with-method-overloading">Step 3: Support Both Versions with Method Overloading</h2>



<p>Overloading is a feature that lets you have multiple methods with the same name, as long as they have different parameters. In our case, it allows us to provide a simple version for one folder, and a recursive version with a depth limit. Xojo will automatically pick the right one based on the arguments you pass. You can read more about it in the&nbsp;<a href="https://documentation.xojo.com/getting_started/object-oriented_programming/oop_design_concepts.html#getting-started-object-oriented-programming-oop-design-concepts-overloading" target="_blank" rel="noreferrer noopener">Xojo Documentation</a>.</p>



<p>The default version (depth 0):</p>



<pre class="wp-block-code"><code>Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String) As SearchHit()
  Return FindInFiles(targetFolder, searchTerm, 0)
End Function</code></pre>



<p>The recursive version:</p>



<pre class="wp-block-code"><code>Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String, maxDepth As Integer) As SearchHit()
  Var hits() As SearchHit
  If targetFolder Is Nil Or Not targetFolder.Exists Or Not targetFolder.IsFolder Or searchTerm.IsEmpty Then
    Return hits
  End If
  Try
    SearchFolder(targetFolder, searchTerm, hits, maxDepth)
  Catch error As IOException
    // Skip inaccessible folders
  End Try
  Return hits
End Function</code></pre>



<p id="why-this-structure">By separating the validation, traversal, and processing, the code is much easier to modify. If you want to add Regex support, you only change&nbsp;<code>SearchFile</code>. If you want to filter by file extension, you only change&nbsp;<code>SearchFolder</code>.</p>



<h2 class="wp-block-heading" id="summary">Summary</h2>



<p>Now, you can search a single folder just like before, or crawl a directory tree with one extra parameter:</p>



<pre class="wp-block-code"><code>// Search up to 3 levels deep
Var results() As SearchUtils.SearchHit = SearchUtils.FindInFiles(myFolder, "TODO", 3)</code></pre>



<h2 class="wp-block-heading" id="the-complete-recursive-code">The Complete Recursive Code</h2>



<h3 class="wp-block-heading" id="the-searchutils-module">The SearchUtils Module</h3>



<pre class="wp-block-code"><code>Module SearchUtils

  Class SearchHit
    Public Property FilePath As String
    Public Property LineNumber As Integer
    Public Property LineText As String

    Public Sub Constructor(filePath As String, lineNumber As Integer, lineText As String)
      Self.FilePath = filePath
      Self.LineNumber = lineNumber
      Self.LineText = lineText
    End Sub
  End Class

  Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String) As SearchHit()
    // Simple usage: depth = 0
    Return FindInFiles(targetFolder, searchTerm, 0)
  End Function

  Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String, maxDepth As Integer) As SearchHit()
    // Recursive usage: maxDepth ( -1 is unlimited )

    Var hits() As SearchHit

    If targetFolder Is Nil Or Not targetFolder.Exists Or Not targetFolder.IsFolder Or searchTerm.IsEmpty Then
      Return hits
    End If

    Try
      SearchFolder(targetFolder, searchTerm, hits, maxDepth)
    Catch error As IOException
      // Skip inaccessible folders
    End Try

    Return hits
  End Function

  Private Sub SearchFolder(folder As FolderItem, searchTerm As String, hits() As SearchHit, maxDepth As Integer)
    Try
      For Each item As FolderItem In folder.Children
        If item Is Nil Or Not item.Exists Or Not item.IsReadable Then Continue

        If item.IsFolder Then
          If maxDepth &lt;&gt; 0 Then
            Var nextDepth As Integer = If(maxDepth &gt; 0, maxDepth - 1, maxDepth)
            SearchFolder(item, searchTerm, hits, nextDepth)
          End If
        Else
          SearchFile(item, searchTerm, hits)
        End If
      Next
    Catch error As IOException
      // Skip inaccessible folders
    End Try
  End Sub

  Private Sub SearchFile(file As FolderItem, searchTerm As String, hits() As SearchHit)
    Try
      Var input As TextInputStream = TextInputStream.Open(file)
      input.Encoding = Encodings.UTF8
      Var lineNumber As Integer = 0

      While Not input.EndOfFile
        Var line As String = input.ReadLine
        lineNumber = lineNumber + 1

        If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
          hits.Add(New SearchHit(file.NativePath, lineNumber, line))
        End If
      Wend

      input.Close
    Catch error As IOException
      // Skip unreadable files
    End Try
  End Sub
End Module</code></pre>



<p>Happy coding!</p>



<p><em>Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!</em></p>



<ul class="wp-block-social-links has-normal-icon-size is-content-justification-center is-layout-flex wp-container-core-social-links-is-layout-16018d1d wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-facebook  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.facebook.com/goxojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 2C6.5 2 2 6.5 2 12c0 5 3.7 9.1 8.4 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.3v7C18.3 21.1 22 17 22 12c0-5.5-4.5-10-10-10z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Facebook</span></a></li>

<li class="wp-social-link wp-social-link-x  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://x.com/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M13.982 10.622 20.54 3h-1.554l-5.693 6.618L8.745 3H3.5l6.876 10.007L3.5 21h1.554l6.012-6.989L15.868 21h5.245l-7.131-10.378Zm-2.128 2.474-.697-.997-5.543-7.93H8l4.474 6.4.697.996 5.815 8.318h-2.387l-4.745-6.787Z" /></svg><span class="wp-block-social-link-label screen-reader-text">X</span></a></li>

<li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.linkedin.com/company/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-github  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://github.com/topics/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12,2C6.477,2,2,6.477,2,12c0,4.419,2.865,8.166,6.839,9.489c0.5,0.09,0.682-0.218,0.682-0.484 c0-0.236-0.009-0.866-0.014-1.699c-2.782,0.602-3.369-1.34-3.369-1.34c-0.455-1.157-1.11-1.465-1.11-1.465 c-0.909-0.62,0.069-0.608,0.069-0.608c1.004,0.071,1.532,1.03,1.532,1.03c0.891,1.529,2.341,1.089,2.91,0.833 c0.091-0.647,0.349-1.086,0.635-1.337c-2.22-0.251-4.555-1.111-4.555-4.943c0-1.091,0.39-1.984,1.03-2.682 C6.546,8.54,6.202,7.524,6.746,6.148c0,0,0.84-0.269,2.75,1.025C10.295,6.95,11.15,6.84,12,6.836 c0.85,0.004,1.705,0.114,2.504,0.336c1.909-1.294,2.748-1.025,2.748-1.025c0.546,1.376,0.202,2.394,0.1,2.646 c0.64,0.699,1.026,1.591,1.026,2.682c0,3.841-2.337,4.687-4.565,4.935c0.359,0.307,0.679,0.917,0.679,1.852 c0,1.335-0.012,2.415-0.012,2.741c0,0.269,0.18,0.579,0.688,0.481C19.138,20.161,22,16.416,22,12C22,6.477,17.523,2,12,2z"></path></svg><span class="wp-block-social-link-label screen-reader-text">GitHub</span></a></li>

<li class="wp-social-link wp-social-link-youtube  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.youtube.com/c/XojoInc" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M21.8,8.001c0,0-0.195-1.378-0.795-1.985c-0.76-0.797-1.613-0.801-2.004-0.847c-2.799-0.202-6.997-0.202-6.997-0.202 h-0.009c0,0-4.198,0-6.997,0.202C4.608,5.216,3.756,5.22,2.995,6.016C2.395,6.623,2.2,8.001,2.2,8.001S2,9.62,2,11.238v1.517 c0,1.618,0.2,3.237,0.2,3.237s0.195,1.378,0.795,1.985c0.761,0.797,1.76,0.771,2.205,0.855c1.6,0.153,6.8,0.201,6.8,0.201 s4.203-0.006,7.001-0.209c0.391-0.047,1.243-0.051,2.004-0.847c0.6-0.607,0.795-1.985,0.795-1.985s0.2-1.618,0.2-3.237v-1.517 C22,9.62,21.8,8.001,21.8,8.001z M9.935,14.594l-0.001-5.62l5.404,2.82L9.935,14.594z"></path></svg><span class="wp-block-social-link-label screen-reader-text">YouTube</span></a></li></ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Build a Simple Find in Files Function</title>
		<link>https://blog.xojo.com/2026/04/02/build-a-simple-find-in-files-function/</link>
		
		<dc:creator><![CDATA[Gabriel Ludosanu]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 16:15:00 +0000</pubDate>
				<category><![CDATA[Cross-Platform]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Files]]></category>
		<category><![CDATA[Search]]></category>
		<category><![CDATA[Text Files]]></category>
		<category><![CDATA[Text Processing]]></category>
		<guid isPermaLink="false">https://blog.xojo.com/?p=15972</guid>

					<description><![CDATA[Sooner or later most apps need some version of the ability to find text in those files, whether you&#8217;re scanning log files, config files, exported&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Sooner or later most apps need some version of the ability to find text in those files, whether you&#8217;re scanning log files, config files, exported data, or source code.</p>



<p>In this post, we&#8217;ll build a simple Find in Files utility in Xojo that:</p>



<ul class="wp-block-list">
<li>searches the files in a folder</li>



<li>reads each file line by line</li>



<li>performs a plain-text, case-insensitive match</li>



<li>returns the file path, line number, and matching line</li>
</ul>



<p>The goal is simple: build something useful while leaving the door open for the fancier stuff.</p>



<h2 class="wp-block-heading" id="what-this-find-in-files-function-will-do">What this Find in Files function will do</h2>



<p>Let&#8217;s keep the scope clear:</p>



<ul class="wp-block-list">
<li>search one folder</li>



<li>search plain text only</li>



<li>skip subfolders for now</li>



<li>return the matching file path, line number, and line contents</li>
</ul>



<h2 class="wp-block-heading" id="step-1-put-the-result-class-in-the-module">Step 1: A class in a module and some properties</h2>



<p>Before touching the file system, let&#8217;s define what a match looks like.</p>



<p>You could return raw strings, but that gets messy fast. A small class keeps the code readable and gives us an obvious place to extend later. In this version, I like keeping that class inside the same module as the search function. It keeps the entire utility self-contained instead of spraying helper types all over the project.</p>



<p>So, we start by adding a module&nbsp;<code>SearchUtils</code>&nbsp;and inside the module we will add a class&nbsp;<code>SearchHit</code>.</p>



<pre class="wp-block-code"><code>Module SearchUtils

  Class SearchHit

    Public Property FilePath As String
    Public Property LineNumber As Integer
    Public Property LineText As String

    Public Sub Constructor(filePath As String, lineNumber As Integer, lineText As String)
      Self.FilePath = filePath
      Self.LineNumber = lineNumber
      Self.LineText = lineText
    End Sub

  End Class

End Module</code></pre>



<p>Each result stores three things:</p>



<ol class="wp-block-list">
<li>the file path</li>



<li>the line number</li>



<li>the matching line</li>
</ol>



<h2 class="wp-block-heading" id="step-2-walk-through-the-folder">Step 2: Walk through the folder</h2>



<p><code>FolderItem.Children</code> lets us loop through a folder with a <code>For Each</code> loop, which keeps the code clear.</p>



<p>We&#8217;ll also reject bad input early:</p>



<ul class="wp-block-list">
<li>the folder is&nbsp;<code>Nil</code></li>



<li>the folder does not exist</li>



<li>the item passed is not actually a folder</li>



<li>the search term is empty</li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="409" src="https://blog.xojo.com/wp-content/uploads/2026/03/find_in_files_xojo_function_diagram2-1024x409.png" alt="" class="wp-image-16027" srcset="https://blog.xojo.com/wp-content/uploads/2026/03/find_in_files_xojo_function_diagram2-1024x409.png 1024w, https://blog.xojo.com/wp-content/uploads/2026/03/find_in_files_xojo_function_diagram2-300x120.png 300w, https://blog.xojo.com/wp-content/uploads/2026/03/find_in_files_xojo_function_diagram2-768x307.png 768w, https://blog.xojo.com/wp-content/uploads/2026/03/find_in_files_xojo_function_diagram2.png 1536w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>This process is simple, fast and effective.</p>



<h2 class="wp-block-heading" id="step-3-read-files-line-by-line">Step 3: Read files line by line</h2>



<p>For text files,&nbsp;<code>TextInputStream.Open</code>&nbsp;is the right tool. From there, we can call&nbsp;<code>ReadLine</code>&nbsp;until&nbsp;<code>EndOfFile</code>&nbsp;becomes&nbsp;<code>True</code>.</p>



<p>I prefer this over&nbsp;<code>ReadAll</code>&nbsp;for a utility like this.&nbsp;<code>ReadAll</code>&nbsp;is fine when the file is small and you know what you&#8217;re doing, but line-by-line reading is a better default here. It keeps memory use in check and gives us line numbers for free.</p>



<p>For the sample code, I&#8217;m explicitly using UTF-8:</p>



<pre class="wp-block-code"><code>input.Encoding = Encodings.UTF8</code></pre>



<p>That keeps the sample predictable. It does&nbsp;<strong>not</strong>&nbsp;mean this code can gracefully decode every text file you throw at it. Files with unknown encodings or binary-ish content are a separate problem, and they deserve their own solution.</p>



<h2 class="wp-block-heading" id="step-4-match-the-text">Step 4: Match the text</h2>



<p>For a plain-text search,&nbsp;<code>String.Contains</code>&nbsp;does exactly what we need:</p>



<pre class="wp-block-code"><code>line.Contains(searchTerm, ComparisonOptions.CaseInsensitive)</code></pre>



<p>It handles the case-insensitivity without the overhead, or the headache, of regex.</p>



<h2 class="wp-block-heading" id="step-5-the-full-module">Step 5: The full module and class and function</h2>



<pre class="wp-block-code"><code>Module SearchUtils

  Public Class SearchHit
    Public Property FilePath As String
    Public Property LineNumber As Integer
    Public Property LineText As String

    Public Sub Constructor(filePath As String, lineNumber As Integer, lineText As String)
      Self.FilePath = filePath
      Self.LineNumber = lineNumber
      Self.LineText = lineText
    End Sub
  End Class

  Public Function FindInFiles(targetFolder As FolderItem, searchTerm As String) As SearchHit()
    Var hits() As SearchHit

    If targetFolder Is Nil Then
      Return hits
    End If

    If Not targetFolder.Exists Or Not targetFolder.IsFolder Then
      Return hits
    End If

    If searchTerm.IsEmpty Then
      Return hits
    End If

    For Each item As FolderItem In targetFolder.Children
      If item Is Nil Then Continue
      If item.IsFolder Then Continue
      If Not item.Exists Then Continue
      If Not item.IsReadable Then Continue

      Try
        Var input As TextInputStream = TextInputStream.Open(item)
        input.Encoding = Encodings.UTF8

        Var lineNumber As Integer = 0

        While Not input.EndOfFile
          Var line As String = input.ReadLine
          lineNumber = lineNumber + 1

          If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
            hits.Add(New SearchHit(item.NativePath, lineNumber, line))
          End If
        Wend

        input.Close

      Catch error As IOException
        ' Skip files that cannot be read as text.
        Continue
      End Try
    Next

    Return hits
  End Function

End Module</code></pre>



<h2 class="wp-block-heading" id="how-it-works">How it works</h2>



<p>Let&#8217;s break down the important parts.</p>



<h3 class="wp-block-heading" id="input-validation">Input validation</h3>



<p>This block prevents pointless work and avoids avoidable runtime problems:</p>



<pre class="wp-block-code"><code>If targetFolder Is Nil Then
  Return hits
End If

If Not targetFolder.Exists Or Not targetFolder.IsFolder Then
  Return hits
End If

If searchTerm.IsEmpty Then
  Return hits
End If</code></pre>



<p>Rule of thumb: reject bad input early.</p>



<h3 class="wp-block-heading" id="folder-iteration">Folder iteration</h3>



<p>This is the core loop:</p>



<pre class="wp-block-code"><code>For Each item As FolderItem In targetFolder.Children</code></pre>



<p>We&#8217;re only scanning the folder&#8217;s immediate contents. If an item is another folder, we skip it.</p>



<pre class="wp-block-code"><code>If item.IsFolder Then Continue</code></pre>



<h3 class="wp-block-heading" id="safe-text-reading">Safe text reading</h3>



<p>Each file is opened with&nbsp;<code>TextInputStream.Open</code>&nbsp;inside a&nbsp;<code>Try...Catch</code>&nbsp;block:</p>



<pre class="wp-block-code"><code>Try
  Var input As TextInputStream = TextInputStream.Open(item)
  input.Encoding = Encodings.UTF8
  ...
Catch error As IOException
  Continue
End Try</code></pre>



<p>That way, one unreadable file does not take down the whole search. It gets skipped and the rest of the folder still gets processed.</p>



<h3 class="wp-block-heading" id="line-by-line-matching">Line-by-line matching</h3>



<p>This is where the actual work happens:</p>



<pre class="wp-block-code"><code>While Not input.EndOfFile
  Var line As String = input.ReadLine
  lineNumber = lineNumber + 1

  If line.Contains(searchTerm, ComparisonOptions.CaseInsensitive) Then
    hits.Add(New SearchHit(item.NativePath, lineNumber, line))
  End If
Wend</code></pre>



<p>Because we&#8217;re reading one line at a time, attaching the correct line number is trivial.</p>



<h3 class="wp-block-heading" id="why-keep-searchhit-inside-the-module">Why keep&nbsp;<code>SearchHit</code>&nbsp;inside the module?</h3>



<p>Because it belongs to this utility. The search function and its result type are part of the same little unit of behavior, so keeping them together makes the project easier to scan and easier to transform it to a&nbsp;<a href="https://documentation.xojo.com/topics/code_management/sharing_code_among_multiple_projects.html" target="_blank" rel="noreferrer noopener">Library</a>.</p>



<p>It also means code outside the module uses the namespaced type:</p>



<pre class="wp-block-code"><code>Var results() As SearchUtils.SearchHit = SearchUtils.FindInFiles(folder, "xojo")</code></pre>



<h2 class="wp-block-heading" id="using-the-function">Using the function</h2>



<p>Here&#8217;s a simple example that lets the user choose a folder and then writes the results to the debug log:</p>



<pre class="wp-block-code"><code>Var folder As FolderItem = FolderItem.ShowSelectFolderDialog
If folder Is Nil Then Return

Var results() As SearchUtils.SearchHit = SearchUtils.FindInFiles(folder, "error")

For Each hit As SearchUtils.SearchHit In results
  System.DebugLog(hit.FilePath + " | line " + hit.LineNumber.ToString + " | " + hit.LineText)
Next</code></pre>



<p>If you want to display the results in a&nbsp;<code>DesktopListBox</code>, that works nicely too:</p>



<pre class="wp-block-code"><code>Var folder As FolderItem = FolderItem.ShowSelectFolderDialog
If folder Is Nil Then Return

Var results() As SearchUtils.SearchHit = FindInFiles(folder, "error")

ListBox1.RemoveAllRows
ListBox1.ColumnCount = 3

For Each hit As SearchUtils.SearchHit In results
  ListBox1.AddRow(hit.FilePath)
  ListBox1.CellTextAt(ListBox1.LastAddedRowIndex, 1) = hit.LineNumber.ToString
  ListBox1.CellTextAt(ListBox1.LastAddedRowIndex, 2) = hit.LineText
Next</code></pre>



<p>A few sample results might look like this:</p>



<pre class="wp-block-code"><code>C:\Logs\app.log | line 18 | Error connecting to database
C:\Logs\app.log | line 42 | Error writing audit record
C:\Configs\service.txt | line 7 | Last error message: timeout</code></pre>



<h2 class="wp-block-heading" id="practical-limitations-of-this-version">Practical limitations of this version</h2>



<p>This utility is useful, but limited.</p>



<h3 class="wp-block-heading" id="it-assumes-utf-8">It assumes UTF-8</h3>



<p>That is fine for plenty of modern text files, but not all of them. If you&#8217;re dealing with mixed encodings, you&#8217;ll need a smarter approach that detects the file encoding.</p>



<h3 class="wp-block-heading" id="it-doesnt-recurse-into-subfolders">It doesn&#8217;t recurse into subfolders</h3>



<p>That is deliberate. Recursive search is useful, but it adds another layer of behavior and another place to make the code harder to read.</p>



<h2 class="wp-block-heading" id="final-thoughts">Final thoughts</h2>



<p>A basic Find in Files function is one of those utilities that looks small but pays off quickly. This is a base-layer utility. Once you have this working, adding recursion or regex is a much smaller lift.</p>



<p>Let&#8217;s discuss in the&nbsp;<a href="https://forum.xojo.com/">forums</a>.</p>



<p><em>Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!</em></p>



<ul class="wp-block-social-links has-normal-icon-size is-content-justification-center is-layout-flex wp-container-core-social-links-is-layout-16018d1d wp-block-social-links-is-layout-flex"><li class="wp-social-link wp-social-link-facebook  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.facebook.com/goxojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 2C6.5 2 2 6.5 2 12c0 5 3.7 9.1 8.4 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.3v7C18.3 21.1 22 17 22 12c0-5.5-4.5-10-10-10z"></path></svg><span class="wp-block-social-link-label screen-reader-text">Facebook</span></a></li>

<li class="wp-social-link wp-social-link-x  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://x.com/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M13.982 10.622 20.54 3h-1.554l-5.693 6.618L8.745 3H3.5l6.876 10.007L3.5 21h1.554l6.012-6.989L15.868 21h5.245l-7.131-10.378Zm-2.128 2.474-.697-.997-5.543-7.93H8l4.474 6.4.697.996 5.815 8.318h-2.387l-4.745-6.787Z" /></svg><span class="wp-block-social-link-label screen-reader-text">X</span></a></li>

<li class="wp-social-link wp-social-link-linkedin  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.linkedin.com/company/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z"></path></svg><span class="wp-block-social-link-label screen-reader-text">LinkedIn</span></a></li>

<li class="wp-social-link wp-social-link-github  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://github.com/topics/xojo" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12,2C6.477,2,2,6.477,2,12c0,4.419,2.865,8.166,6.839,9.489c0.5,0.09,0.682-0.218,0.682-0.484 c0-0.236-0.009-0.866-0.014-1.699c-2.782,0.602-3.369-1.34-3.369-1.34c-0.455-1.157-1.11-1.465-1.11-1.465 c-0.909-0.62,0.069-0.608,0.069-0.608c1.004,0.071,1.532,1.03,1.532,1.03c0.891,1.529,2.341,1.089,2.91,0.833 c0.091-0.647,0.349-1.086,0.635-1.337c-2.22-0.251-4.555-1.111-4.555-4.943c0-1.091,0.39-1.984,1.03-2.682 C6.546,8.54,6.202,7.524,6.746,6.148c0,0,0.84-0.269,2.75,1.025C10.295,6.95,11.15,6.84,12,6.836 c0.85,0.004,1.705,0.114,2.504,0.336c1.909-1.294,2.748-1.025,2.748-1.025c0.546,1.376,0.202,2.394,0.1,2.646 c0.64,0.699,1.026,1.591,1.026,2.682c0,3.841-2.337,4.687-4.565,4.935c0.359,0.307,0.679,0.917,0.679,1.852 c0,1.335-0.012,2.415-0.012,2.741c0,0.269,0.18,0.579,0.688,0.481C19.138,20.161,22,16.416,22,12C22,6.477,17.523,2,12,2z"></path></svg><span class="wp-block-social-link-label screen-reader-text">GitHub</span></a></li>

<li class="wp-social-link wp-social-link-youtube  wp-block-social-link"><a rel="noopener nofollow" target="_blank" href="https://www.youtube.com/c/XojoInc" class="wp-block-social-link-anchor"><svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M21.8,8.001c0,0-0.195-1.378-0.795-1.985c-0.76-0.797-1.613-0.801-2.004-0.847c-2.799-0.202-6.997-0.202-6.997-0.202 h-0.009c0,0-4.198,0-6.997,0.202C4.608,5.216,3.756,5.22,2.995,6.016C2.395,6.623,2.2,8.001,2.2,8.001S2,9.62,2,11.238v1.517 c0,1.618,0.2,3.237,0.2,3.237s0.195,1.378,0.795,1.985c0.761,0.797,1.76,0.771,2.205,0.855c1.6,0.153,6.8,0.201,6.8,0.201 s4.203-0.006,7.001-0.209c0.391-0.047,1.243-0.051,2.004-0.847c0.6-0.607,0.795-1.985,0.795-1.985s0.2-1.618,0.2-3.237v-1.517 C22,9.62,21.8,8.001,21.8,8.001z M9.935,14.594l-0.001-5.62l5.404,2.82L9.935,14.594z"></path></svg><span class="wp-block-social-link-label screen-reader-text">YouTube</span></a></li></ul>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
