Link to home
Start Free TrialLog in
Avatar of Peter Allen
Peter AllenFlag for United States of America

asked on

Get list of Drives in VB.NET CheckedListBox as DriveInfo List

Experts,

I have a recursive function that locates all files with a particular extension on all drives.  

What I'd like to do its to limit the search to selected drives based on a CheckedListBox.  The CheckedListBox shows all drive letters that are "Ready".  The user selects the drives to search and then the BackgroundWorker goes to work locating the matches and places a table of matches in a TreeView.  So if I select "C:\" only then the search will only be for drive C:.

What I did was to add a Listbox showing only the selected drives.  From this list I want to build a DriveInfo list to pass to the Backgroundworker.  This was an interum step to see if I could get the code to work to build the DriveInfo list without the second listbox at all.

Can anyone assist me with this?  Thanks so much!

Here is the code I have thus far:
        Dim myArgs As bgwArguments = CType(e.Argument, bgwArguments)
        Dim lsDrives As List(Of DriveInfo) = myArgs.lstDrives
        Dim sFilter As String = myArgs.sFilter
        Dim lsCount As Integer = myArgs.lstDrives.Count
        For iLoop As Integer = 0 To myArgs.lstDrives.Count - 1
            'For Each lsDrives As DriveInfo In My.Computer.FileSystem.Drives
            If bgSearch.CancellationPending Then
                lblStatus.Text = "Waiting for user input..."
                Exit Sub
            End If
            'If Not drive.IsReady Then Continue For
            If Not myArgs.lstDrives(iLoop).IsReady Then Continue For
            SearchRecursive(myArgs.lstDrives(iLoop).RootDirectory.FullName, sFilter)
        Next

Open in new window


Here is the code from the DoWork sub:
        Dim myArgs As bgwArguments = CType(e.Argument, bgwArguments)
        Dim lsDrives As List(Of DriveInfo) = myArgs.lstDrives
        Dim sFilter As String = myArgs.sFilter
        Dim lsCount As Integer = myArgs.lstDrives.Count
        For iLoop As Integer = 0 To myArgs.lstDrives.Count - 1
            'For Each lsDrives As DriveInfo In My.Computer.FileSystem.Drives
            If bgSearch.CancellationPending Then
                lblStatus.Text = "Waiting for user input..."
                Exit Sub
            End If
            'If Not drive.IsReady Then Continue For
            If Not myArgs.lstDrives(iLoop).IsReady Then Continue For
            SearchRecursive(myArgs.lstDrives(iLoop).RootDirectory.FullName, sFilter)
        Next

Open in new window


The "myArgs.lstDrives(iLoop)" arguments contains the DriveInfo list to search.  That is what I want to create in the first code block shown above.
Avatar of it_saige
it_saige
Flag of United States of America image

Something like:

Form1.vb -
Imports System.ComponentModel
Imports System.IO

Public Class Form1
	Private binder As New BindingSource
	Private drives As New BindingList(Of DriveInfo)

	Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
		btnCancel.Enabled = False
		For Each drive As DriveInfo In DriveInfo.GetDrives().Where(Function(x) x.IsReady)
			drives.Add(drive)
		Next
		binder.DataSource = drives
		lbDrives.DataSource = binder
	End Sub

	Private Sub OnClick(sender As Object, e As EventArgs) Handles btnCancel.Click, btnStart.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			btn.Enabled = False
			If btn.Equals(btnStart) Then
				lbDrives.Enabled = False
				BackgroundWorker1.RunWorkerAsync(New List(Of DriveInfo)(From drive As DriveInfo In lbDrives.CheckedItems Select drive))
				btnCancel.Enabled = True
			ElseIf btn.Equals(btnCancel) Then
				BackgroundWorker1.CancelAsync()
			End If
		End If
	End Sub

	Private Sub OnDoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
		If TypeOf sender Is BackgroundWorker Then
			Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
			If e.Argument IsNot Nothing AndAlso TypeOf e.Argument Is IEnumerable(Of DriveInfo) Then
				Dim drives As IEnumerable(Of DriveInfo) = DirectCast(e.Argument, IEnumerable(Of DriveInfo))
				Dim counter As Integer = 1
				For Each drive As DriveInfo In drives
					If Not worker.CancellationPending Then
						worker.ReportProgress(counter / drives.Count * 100.0F, drive)
						counter = counter + 1
						Threading.Thread.Sleep(5000)
					Else
						e.Cancel = True
						Return
					End If
				Next
			End If
		End If
	End Sub

	Private Sub OnProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
		If Not e.UserState Is Nothing AndAlso TypeOf e.UserState Is DriveInfo Then
			Dim drive = DirectCast(e.UserState, DriveInfo)
			lbProgress.Text = String.Format("Searching drive {0}", drive.Name)
		End If
	End Sub

	Private Sub OnWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
		If e.Cancelled = True Then
			lbProgress.Text = "Canceled!"
		ElseIf e.Error IsNot Nothing Then
			lbProgress.Text = "Error: " & e.Error.Message
		Else
			lbProgress.Text = "Done!"
		End If
		For Each index As Integer In lbDrives.CheckedIndices
			lbDrives.SetItemChecked(index, False)
		Next
		btnStart.Enabled = True
		btnCancel.Enabled = False
		lbDrives.Enabled = True
	End Sub
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
		Me.BackgroundWorker1 = New System.ComponentModel.BackgroundWorker()
		Me.lbDrives = New System.Windows.Forms.CheckedListBox()
		Me.btnStart = New System.Windows.Forms.Button()
		Me.btnCancel = New System.Windows.Forms.Button()
		Me.lbProgress = New System.Windows.Forms.Label()
		Me.SuspendLayout()
		'
		'BackgroundWorker1
		'
		Me.BackgroundWorker1.WorkerReportsProgress = True
		Me.BackgroundWorker1.WorkerSupportsCancellation = True
		'
		'lbDrives
		'
		Me.lbDrives.FormattingEnabled = True
		Me.lbDrives.Location = New System.Drawing.Point(13, 13)
		Me.lbDrives.Name = "lbDrives"
		Me.lbDrives.Size = New System.Drawing.Size(259, 199)
		Me.lbDrives.TabIndex = 0
		'
		'btnStart
		'
		Me.btnStart.Location = New System.Drawing.Point(116, 226)
		Me.btnStart.Name = "btnStart"
		Me.btnStart.Size = New System.Drawing.Size(75, 23)
		Me.btnStart.TabIndex = 1
		Me.btnStart.Text = "Start Search"
		Me.btnStart.UseVisualStyleBackColor = True
		'
		'btnCancel
		'
		Me.btnCancel.Location = New System.Drawing.Point(197, 225)
		Me.btnCancel.Name = "btnCancel"
		Me.btnCancel.Size = New System.Drawing.Size(75, 23)
		Me.btnCancel.TabIndex = 2
		Me.btnCancel.Text = "Cancel"
		Me.btnCancel.UseVisualStyleBackColor = True
		'
		'lbProgress
		'
		Me.lbProgress.AutoSize = True
		Me.lbProgress.Location = New System.Drawing.Point(13, 226)
		Me.lbProgress.Name = "lbProgress"
		Me.lbProgress.Size = New System.Drawing.Size(39, 13)
		Me.lbProgress.TabIndex = 3
		Me.lbProgress.Text = "Label1"
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 261)
		Me.Controls.Add(Me.lbProgress)
		Me.Controls.Add(Me.btnCancel)
		Me.Controls.Add(Me.btnStart)
		Me.Controls.Add(Me.lbDrives)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.ResumeLayout(False)
		Me.PerformLayout()

	End Sub
	Friend WithEvents BackgroundWorker1 As System.ComponentModel.BackgroundWorker
	Friend WithEvents lbDrives As System.Windows.Forms.CheckedListBox
	Friend WithEvents btnStart As System.Windows.Forms.Button
	Friend WithEvents btnCancel As System.Windows.Forms.Button
	Friend WithEvents lbProgress As System.Windows.Forms.Label

End Class

Open in new window

Which produces the following output -User generated imageUser generated imageUser generated imageUser generated image-saige-
Avatar of Peter Allen

ASKER

-saige-

That's Awesome!

Would it be possible to add a ProgressBar to this?
ASKER CERTIFIED SOLUTION
Avatar of it_saige
it_saige
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
it_saige,

That's sweet!  I do have two more related questions.  The arguments that are passed to the Backgroundworker:

In the code you have:
  Private Sub OnClick(sender As Object, e As EventArgs) Handles btnCancel.Click, btnStart.Click

        If TypeOf sender Is Button Then
            Dim btn = DirectCast(sender, Button)
            btn.Enabled = False
            If btn.Equals(btnStart) Then
                lbDrives.Enabled = False
                pbStatus.Visible = True
                lblStatus.Text = ""
                BackgroundWorker1.RunWorkerAsync(New List(Of DriveInfo)(From drive As DriveInfo In lbDrives.CheckedItems Select drive))
                btnCancel.Enabled = True
            ElseIf btn.Equals(btnCancel) Then
                BackgroundWorker1.CancelAsync()
            End If
        End If

    End Sub

Open in new window


You pass one argument: "New List(Of DriveInfo)(From drive As DriveInfo In lbDrives.CheckedItems Select drive)"

This argument passes the list of Driveinfo and uses Select to indicate only the selected Drives?
What about passing multiple arguments like the following:
    Partial Public Class mybgwArguments
        Public lstDrives As List(Of DriveInfo)
        Public sFilter As BindingList(Of DriveInfo)
    End Class

So would the RunWorkerAsync code be:

BackgroundWorker1.RunWorkerAsync(mybgwArguments)?

Open in new window


And how would I break down the arguments in the BackgroundWorker DoWork event:  Maybe something like:

        If TypeOf sender Is BackgroundWorker Then
            Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
            Dim lstdrives As New BindingList(Of DriveInfo)
            Dim sFilter As String = Nothing
            If e.Argument IsNot Nothing Then
                'If e.Argument IsNot Nothing AndAlso TypeOf e.Argument Is IEnumerable(Of DriveInfo) Then
                Dim args = DirectCast(e.Argument, mybgwArguments)
                Dim drives As IEnumerable(Of DriveInfo) = DirectCast(mybgwArguments.lstDrives, IEnumerable(Of DriveInfo))
                Dim sFilter As String = DirectCast(mybgwArguments.sFilter, String)
                Dim counter As Integer = 1
                For Each drive As DriveInfo In drives
                    If Not worker.CancellationPending Then
                        worker.ReportProgress(counter / drives.Count * 100.0F, drive)
                        counter = counter + 1
                        Threading.Thread.Sleep(5000)
                    Else
                        e.Cancel = True
                        Return
                    End If
                Next
            End If
        End If

Open in new window


sFilter happens to be the search criteria like "*.* or *.MDF, etc.

I get two errors on "mybgwArguments.lstDrives" and "mybgwArguments.sFilter".  Both errors say "Reference to a non-shared member requires an object reference".
The answer to your question is basically, you want to use a *New* instance of something (in this case mybgwArguments).

Something like:
Imports System.ComponentModel
Imports System.IO

Public Class Form1
	Private binder As New BindingSource
	Private drives As New BindingList(Of DriveInfo)

	Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
		pbStatus.Visible = False
		pbStatus.Value = 0
		btnCancel.Enabled = False
		For Each drive As DriveInfo In DriveInfo.GetDrives().Where(Function(x) x.IsReady)
			drives.Add(drive)
		Next
		binder.DataSource = drives
		lbDrives.DataSource = binder
	End Sub

	Private Sub OnClick(sender As Object, e As EventArgs) Handles btnCancel.Click, btnStart.Click
		If TypeOf sender Is Button Then
			Dim btn = DirectCast(sender, Button)
			btn.Enabled = False
			If btn.Equals(btnStart) Then
				lbDrives.Enabled = False
				pbStatus.Visible = True
				lblStatus.Text = ""
				'BackgroundWorker1.RunWorkerAsync(New List(Of DriveInfo)(From drive As DriveInfo In lbDrives.CheckedItems Select drive))
				Dim searchers As New List(Of DriveSearcher)
				For Each di As DriveInfo In lbDrives.CheckedItems
					searchers.Add(New DriveSearcher() With {.Drive = di, .Filter = "*.*"})
				Next
				BackgroundWorker1.RunWorkerAsync(searchers)
				btnCancel.Enabled = True
			ElseIf btn.Equals(btnCancel) Then
				BackgroundWorker1.CancelAsync()
			End If
		End If
	End Sub

	Private Sub OnDoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
		If TypeOf sender Is BackgroundWorker Then
			Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
			If e.Argument IsNot Nothing AndAlso TypeOf e.Argument Is IEnumerable(Of DriveSearcher) Then
				Dim searchers As IEnumerable(Of DriveSearcher) = DirectCast(e.Argument, IEnumerable(Of DriveSearcher))
				Dim counter As Integer = 1
				For Each searcher As DriveSearcher In searchers
					If Not worker.CancellationPending Then
						worker.ReportProgress(counter / drives.Count * 100.0F, searcher.Drive)
						counter = counter + 1
						Threading.Thread.Sleep(5000)
					Else
						e.Cancel = True
						Return
					End If
				Next
			End If
		End If
	End Sub

	Private Sub OnProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
		If Not e.UserState Is Nothing AndAlso TypeOf e.UserState Is DriveInfo Then
			Dim drive = DirectCast(e.UserState, DriveInfo)
			pbStatus.Value = e.ProgressPercentage
			lblStatus.Text = String.Format("Searching drive {0}", drive.Name)
		End If
	End Sub

	Private Sub OnWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
		If e.Cancelled = True Then
			lblStatus.Text = "Canceled!"
		ElseIf e.Error IsNot Nothing Then
			lblStatus.Text = "Error: " & e.Error.Message
		Else
			lblStatus.Text = "Done!"
		End If
		For Each index As Integer In lbDrives.CheckedIndices
			lbDrives.SetItemChecked(index, False)
		Next
		btnStart.Enabled = True
		btnCancel.Enabled = False
		lbDrives.Enabled = True
		pbStatus.Visible = False
		pbStatus.Value = 0
	End Sub
End Class

Class DriveSearcher
	Public Property Drive() As DriveInfo
	Public Property Filter() As String
End Class

Open in new window

But this makes me think, if all of the drives will have the same filter then why require it as an argument, unless they won't.  This then raises the following question:

What determines the filter?

In either case, that being a user interactive or a logic defined process, it would ultimately make more sense that your databound list be a list of the complex object and not DriveInfo.  At that point it now becomes a matter of ensuring that the user interface, if needed, is wired up to display the filter property and allow the user to modify (again if needed).

-saige-
-saige-

The reason 'sFilter' is being passed is so I could look for a specific type of file.  For example: '*.MDF' to look for MSSQL database files, etc.

A user may want to look for the file on specific drives or all drives.  So I give them the option.  The 'sFilter' will be applied to all of the selected drives they look search.

Further, this Search mechanism will be used when I create a Control that will enable me to create template structures in XML to store DataGridView dsigns to access database files for applications I build.  I would be able to locate any type of database with the filter.

This Search method can be used for many other purposes as well.
While I accepted the one solution with ProgressBar as the best one you gave me lots of good things to learn.  I really appreciate you taking the time to assist.

Have a great day and Happy Thanksgiving!!