I am using Visual Studio 2010 and coding in Visual Basic.
I have to display a progress bar while copying a directory.
I have never worked with a progress bar before and not sure where to start.
Here is the code I currently have.
If My.Computer.FileSystem.DirectoryExists(filePath & "IETMS\" & installFile) Then
frmWait.Show()
My.Computer.FileSystem.CopyDirectory(strFileName, filePath & "IETMS", True)
ListView1.Items.Clear()
testReload()
frmWait.Close()
Else
My.Computer.FileSystem.CreateDirectory(filePath & "IETMS\" & installFile)
frmWait.Show()
My.Computer.FileSystem.CopyDirectory(strFileName, filePath & "IETMS", True)
ListView1.Items.Clear()
testReload()
frmWait.Close()
End If
I am assuming that I need to calculate the size of the source folder and then monitor the destination folder size and set the progress bar max to the source folder size and set the value of the progress bar to the destination size, but I am not sure how to go about doing this.
You can count the files in the source directory and then every so often count the files in the destination directory. To count the files in all subdirectories you can use a recursive sub:
Private Sub CountFiles(InFolder As String, ByRef Result As Integer)
Result += IO.Directory.GetFiles(InFolder).Count
For Each f As String In IO.Directory.GetDirectories(InFolder)
CountFiles(f, Result)
Next
End Sub
To use this do
Dim FileCount as Integer = 0
CountFiles("C:\test", FileCount)
Messagebox.Show(FileCount.ToString)
Set the progressbar to the percentage value like pbProgress.Value = CInt(DestCount/SourceCount * 100).
Edit: Following up on your question: You should use for example a backgroundworker, or a task, or a thread, to perform the copy and then update the progressbar in a Timer.
For example you can create a sub that does the copying and then start the sub in a new task:
Private WithEvents tmrUpdatePBG As Timer
Private Sub StartCopy(SourceFolder As String, DestFolder As String)
'copy copy copy
CopyComplete()
End Sub
Private Sub CopyComplete()
tmrUpdatePBG.Stop()
End Sub
[...]
'Whereever you start the copy process
Dim ct As New Task(Sub() StartCopy("C:\source", "C:\dest"))
ct.Start()
tmrUpdatePBG = New Timer
tmrUpdatePBG.Interval = 1000
tmrUpdatePBG.Start()
tmrUpdatePGB would be the timer. In the tick event update the progressbar. It is started when the copying process starts and stops when the process is complete.
I ended up counting the files in the source folder and setting the progress bar max to that number. Then inside the timer I counted the files in the destination folder and set the progress bar value to that number. Then just closed the window I created with the progress bar after the copy was finished.
I also had an issue with the progress bar (not responding) so I put the CopyDirectory inside a BackgroundWorker.
Private Sub tmrWait_Tick(sender As System.Object, e As System.EventArgs) Handles tmrWait.Tick
Dim srcFile As String = strFileName & "\" & installFile
Dim srcDir As New System.IO.DirectoryInfo(srcFile)
Dim srcFolders, srcFiles As Integer
srcFolders = srcDir.GetDirectories.GetUpperBound(0) + 1
srcFiles = srcDir.GetFiles.GetUpperBound(0) + 1
pbInstall.Maximum = srcFolders.ToString()
Dim desFile As String = filePath & "IETMS\" & installFile & "\" & installFile
Dim desDir As New System.IO.DirectoryInfo(desFile)
Dim desFolders, desFiles As Integer
desFolders = desDir.GetDirectories.GetUpperBound(0) + 1
desFiles = desDir.GetFiles.GetUpperBound(0) + 1
pbInstall.Value = desFolders.ToString()
pbInstall.Refresh()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
System.Threading.Thread.Sleep(1000)
My.Computer.FileSystem.CopyDirectory(strFileName, filePath & "IETMS\" & installFile, True)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
frmMain.ListView1.Items.Clear()
frmMain.testReload()
Me.Close()
End Sub
Private Sub frmWait_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Related
So, I am trying to develop a book database. I have created a table with 11 columns which populates a DGV, where only 6 columns are showed. The full data of each book is shown in a lower part of the form, where I have textboxes, which are bounded (BindingSource) to the table, that change as I move in the DGV.
Now, what I want to do is to have the posibility to export/import data from a file.
I have accomplished the exporting part with the following code:
Private Sub BtnExport_Click(sender As Object, e As EventArgs) Handles BtnExport.Click
Dim txt As String = String.Empty
For Each row As DataGridViewRow In DbdocsDataGridView.Rows
For Each cell As DataGridViewCell In row.Cells
'Add the Data rows.
txt += CStr(cell.Value & ","c)
Next
'Add new line.
txt += vbCr & vbLf
Next
Dim folderPath As String = "C:\CSV\"
File.WriteAllText(folderPath & "DataGridViewExport.txt", txt)
End Sub
However, I can't manage to import from the txt. What I've tried is this: https://1bestcsharp.blogspot.com/2018/04/vb.net-import-txt-file-text-to-datagridview.html
It works perfectly if you code the table and it populates de DGV without problem. I can't see how should I adapt that code to my need.
Private Sub BtnImport_Click(sender As Object, e As EventArgs) Handles BtnImport.Click
DbdocsDataGridView.DataSource = table
Dim filas() As String
Dim valores() As String
filas = File.ReadAllLines("C:\CSV\DataGridViewExport.txt")
For i As Integer = 0 To filas.Length - 1 Step +1
valores = filas(i).ToString().Split(",")
Dim row(valores.Length - 1) As String
For j As Integer = 0 To valores.Length - 1 Step +1
row(j) = valores(j).Trim()
Next j
table.Rows.Add(row)
Next i
End Sub
That is what I've tried so far, but I always have an exception arising.
Thanks in advance to anyone who can give me an insight about this.
The DataTable class has built-in methods to load/save data from/to XML called ReadXml and WriteXml. Take a look at example which uses the overload to preserve the schema:
Private ReadOnly dataGridViewExportPath As String = IO.Path.Combine("C:\", "CSV", "DataGridViewExport.txt")
Private Sub BtnExport_Click(sender As Object, e As EventArgs) Handles BtnExport.Click
table.WriteXml(path, XmlWriteMode.WriteSchema)
End Sub
Private Sub BtnImport_Click(sender As Object, e As EventArgs) Handles BtnImport.Click
table.ReadXml(path)
End Sub
While users can manually edit the XML file that is generated from WriteXML, I would certainly not suggest it.
This is code which writes to a text file:
speichern means to save.
Note that I use a # in my example for formatting reasons. When reading in again, you can find the # and then you know that and that line is coming...
And I used Private ReadOnly Deu As New System.Globalization.CultureInfo("de-DE") to format the Date into German format, but you can decide yourself if you want that. 🙂
Note that I'm using a FileDialog that I downloaded from Visual Studio's own NuGet package manager.
Private Sub Button_speichern_Click(sender As Object, e As EventArgs) Handles Button_speichern.Click
speichern()
End Sub
Private Sub speichern()
'-------------------------------------------------------------------------------------------------------------
' User can choose where to save the text file and the program will save it .
'-------------------------------------------------------------------------------------------------------------
Dim Path As String
Using SFD1 As New CommonSaveFileDialog
SFD1.Title = "store data in a text file"
SFD1.Filters.Add(New CommonFileDialogFilter("Textdatei", ".txt"))
SFD1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
If SFD1.ShowDialog() = CommonFileDialogResult.Ok Then
Path = SFD1.FileName & ".txt"
Else
Return
End If
End Using
Using textfile As System.IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(Path, True, System.Text.Encoding.UTF8)
textfile.WriteLine("Timestamp of this file [dd.mm.yyyy hh:mm:ss]: " & Date.Now.ToString("G", Deu) & NewLine & NewLine) 'supposed to be line break + 2 blank lines :-)
textfile.WriteLine("your text")
textfile.WriteLine("#") 'Marker
textfile.Close()
End Using
End Sub
Private Sub FormMain_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
speichern()
End Sub
And this is to read from a text file:
'read all Text
Dim RAT() As String = System.IO.File.ReadAllLines(Pfad, System.Text.Encoding.UTF8)
If RAT.Length = 0 Then Return Nothing
For i As Integer = 0 To RAT.Length - 1 Step 1
If RAT(i) = "#" OrElse RAT(i) = "" Then Continue For
'do your work here with (RAT(i))
Next
I am running code to read text from pdf files and than create a WordCloud. To inform the user the process is on going I add a BackgroundWorker to my form that shows an image saying Loading. I get an error for operating across different threads.
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
If ListBox1.SelectedIndex > -1 Then
For Each Item As Object In ListBox1.SelectedItems
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
Dim unused = MsgBox("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
Else
Dim unused = MsgBox("You have to choose an Archive to create the Word Cloud",, Title)
End If
Size = New Drawing.Size(1400, 800)
End Sub
I put above code in a Private Sub and called it from my BackgroundWorker. The error occured at this line: If ListBox1.SelectedIndex > -1 Then
After trying the suggested code I get the same error again:
Public Sub CreateMyCloud(ByVal sourcePDF As String)
Dim WordsFreqList As New List(Of WordsFrequencies)
For Each line As String In File.ReadLines(sourcePDF)
Dim splitText As String() = line.Split(","c)
If splitText IsNot Nothing AndAlso splitText.Length = 2 Then
Dim wordFrq As New WordsFrequencies
Dim freq As Integer
wordFrq.Word = splitText(0)
wordFrq.Frequency = If(Integer.TryParse(splitText(1), freq), freq, 0)
WordsFreqList.Add(wordFrq)
End If
Next
If WordsFreqList.Count > 0 Then
' Order the list based on the Frequency
WordsFreqList = WordsFreqList.OrderByDescending(Function(w) w.Frequency).ToList
' Add the sorted items to the listview
WordsFreqList.ForEach(Sub(wf)
error -> ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End If
Dim wc As WordCloudGen = New WordCloudGen(600, 400)
Dim i As Image = wc.Draw(WordsFreqList.Select(Function(wf) wf.Word).ToList, WordsFreqList.Select(Function(wf) wf.Frequency).ToList)
ResultPictureBox.Image = i
End Sub
Should look something more like:
Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
If ListBox1.SelectedItems.Count > 0 Then
Dim data As New List(Of Object)
For Each Item As Object In ListBox1.SelectedItems
data.Add(Item)
Next
bTextEmpty = False
ListView1.Items.Clear()
ResultPictureBox.Image = Nothing
BackgroundWorker1.RunWorkerAsync(data)
Else
MessageBox.Show("You have to choose an Archive to create the Word Cloud",, Title)
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim data As List(Of Object) = DirectCast(e.Argument, List(Of Object))
For Each Item As Object In data
Dim ItemSelected = CType(Item("Path"), String)
Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
If myTempFile = False Then
'when we load the form we first call the code to count words in all files in a directory
'lets check if the folder exists
If (Not System.IO.Directory.Exists(ItemSelected)) Then
MessageBox.Show("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
Exit Sub
Else
Call CreateWordList(ItemSelected)
End If
End If
'if the words file is empty we cant create a cloud so exit the sub
If bTextEmpty = True Then Exit Sub
'then we fill the wordcloud and Listview from the created textfile
Call CreateMyCloud(ItemSelected + "\Words.txt")
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Size = New Drawing.Size(1400, 800)
End Sub
To fix the error in your second, edited post:
WordsFreqList.ForEach(Sub(wf)
ListView1.Invoke(Sub()
ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
End Sub)
End Sub)
You cannot simply access controls outside of the current thread. You first need to call the Invoke method on the target control and then pass a delegate containing the instructions intended to modify the control outside of the current thread. See this article on MSDN on how to do this: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8
I have multiple files to upload (to FTP server) using this code:
Private Sub UploadFile(ByVal local As String)
If wc.IsBusy = True Then Throw New Exception("An upload is already ongoing!")
wc.Credentials = New NetworkCredential(usr.ToString, pass.ToString) 'Set the credentials.
'total_dl_size = GetDownloadSize(url) 'Get the size of the current file.
Try
Dim FileName As String = Path.GetFileName(local) 'Get the current file's name.
AppendWarning("Uploading " & FileName & "... ") 'Download notice.
wc.UploadFileAsync(New Uri(info_srv & local), Path.Combine(mc_dir, local)) 'Download the file to the desktop (use your own path here).
Catch ex As Exception
AppendWarning("-ERR: Could not download file: " & local & ControlChars.NewLine)
End Try
End Sub
Private Sub AppendWarning(ByVal Text As String)
If tb_warnings.InvokeRequired Then
tb_warnings.Invoke(Sub() tb_warnings.AppendText(Text))
Else
tb_warnings.AppendText(Text)
End If
End Sub
Private Sub wc_UploadProgressChanged(sender As Object, e As System.Net.UploadProgressChangedEventArgs) Handles wc.UploadProgressChanged
total_ul = e.BytesSent
Dim Progress As Integer = CType(Math.Round((baseline + total_ul) * 100) / total_ul_size, Integer)
If ProgressBar1.InvokeRequired Then
ProgressBar1.Invoke(Sub()
If Progress > 100 Then Progress = 100
If Progress < 0 Then Progress = 0
ProgressBar1.Value = Progress
End Sub)
Else
If Progress > 100 Then Progress = 100
If Progress < 0 Then Progress = 0
ProgressBar1.Value = Progress
End If
If lbl_progress.InvokeRequired Then
lbl_progress.Invoke(Sub() lbl_progress.Text = ((total_ul + baseline) / 1024).ToString("N0") & " KB / " & (total_ul_size / 1024).ToString("N0") & " KB")
Else
lbl_progress.Text = ((total_ul + baseline) / 1024).ToString("N0") & " KB / " & (total_ul_size / 1024).ToString("N0") & " KB | " & Progress.ToString & "%"
End If
End Sub
Private Sub wc_uploadFileCompleted(sender As Object, e As System.ComponentModel.AsyncCompletedEventArgs) Handles wc.UploadDataCompleted
If e.Cancelled Then
MessageBox.Show(e.Cancelled)
ElseIf Not e.Error Is Nothing Then
MessageBox.Show(e.Error.Message)
Else
If files.Count > 0 Then
AppendWarning("Upload Complete!" & ControlChars.NewLine)
baseline = baseline + total_ul
Dim file As String = files.Dequeue()
MsgBox(file)
UploadFile(file) 'Download the next file.
Else
AppendWarning("All Uploads Finished!" & ControlChars.NewLine)
End If
End If
However, using my two test files, it always stops at what would otherwise be the end of the first file I've given it, and doesn't go onto the second one.
However, I have an FTP client connected to this same server, and when I refresh I can see (at least for the first file) the data is being properly uploaded.
Any suggestions as to what's going wrong here?
Edit, log: http://pastebin.com/kqG28NGH
Thank you for any assistance!
This works for me...I tried to mimic what I think is in your form. I tested with a queue of 8 files ranging from 150K to 400K each. I couldn't quite work out what you were trying to do with the progress bar. Mine fills for each file and resets for the next, finishing empty with the last call to DoUpload where there are no more files. Hopefully, this will help.
Imports System.IO
Imports System.Net
Public Class Form1
Const info_srv As String = "ftp://example.com/SomeFolder/"
Const usr As String = ""
Const pass As String = ""
Const mc_dir As String = "D:\Source\Folder"
Private WithEvents wc As New Net.WebClient
' Contains file names only, no paths
Private Files As New Queue(Of String)
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
wc.Credentials = New NetworkCredential(usr, pass)
' Put the work in a task so UI is responsive
Task.Run(Sub() DoUpload())
End Sub
Private Sub DoUpload()
ShowProgress("", 0)
If Files.Count > 0 Then
Dim local As String = Files.Dequeue
Dim FileName As String = Path.Combine(mc_dir, local)
AppendWarning("Uploading " & FileName & "... ")
Try
wc.UploadFileAsync(New Uri(info_srv & local), FileName)
Catch ex As Exception
AppendWarning("-ERR: Could not upload file: " & local & Environment.NewLine)
End Try
Else
AppendWarning("All Uploads Finished!" & Environment.NewLine)
End If
End Sub
Private Sub wc_UploadProgressChanged(sender As Object, e As UploadProgressChangedEventArgs) _
Handles wc.UploadProgressChanged
' Do not use e.ProgressPercentage - it's inaccurate by half by design per Microsoft
With String.Format("{0} KB / {1} KB", Int(e.BytesSent / 1024).ToString("N0"), Int(e.TotalBytesToSend / 1024).ToString("N0"))
ShowProgress(.ToString, Int(e.BytesSent / e.TotalBytesToSend * 100))
End With
End Sub
Private Sub wc_UploadFileCompleted(sender As Object, e As UploadFileCompletedEventArgs) _
Handles wc.UploadFileCompleted
Select Case True
Case e.Cancelled
MessageBox.Show("Cancelled")
Case e.Error IsNot Nothing
MessageBox.Show(e.Error.Message)
Case Else
AppendWarning("Upload Complete!" & Environment.NewLine)
' I needed this just so I could see it work, otherwise too fast
Threading.Thread.Sleep(500)
DoUpload()
End Select
End Sub
Private Sub AppendWarning(ByVal Text As String)
If Me.InvokeRequired Then
Me.Invoke(Sub() AppendWarning(Text))
Else
tb_warnings.AppendText(Text)
End If
End Sub
Private Sub ShowProgress(LabelText As String, Progress As Integer)
If Me.InvokeRequired Then
Me.Invoke(Sub() ShowProgress(LabelText, Progress))
Else
Me.lbl_progress.Text = LabelText
Me.lbl_progress.Refresh()
Me.ProgressBar1.Value = Progress
Me.ProgressBar1.Refresh()
End If
End Sub
End Class
For posterity:
Check your network trace settings in the VB config. I used a really verbose catch-all config I found to do the trace, but it seems the overhead was killing the upload. I've since found a much leaner focus-on-ftp set of xml to modify this and the files now upload properly. Thank you everyone!
Long time reader, first time poster. Usually I'm able to find the answer and make it work. Not this time..... I'm using VB.NET in VS2013. I am trying to update a progress bar with work done in a secondary thread. Easy right? No. I had to make it more complicated. The progress bar (ToolStripProgressBar1) is on the main form (frmMain), the MDI of the project. A secondary form (frmShipping) has a button which initiates a second thread to do some COMM Port communications in a class (cApex). I can get the progress bar to update on the frmMain from the main UI thread (frmShipping button).
This is the code from button on frmShiping and the multithread procedure:
Private Sub btnreadScanner_Click(sender As Object, e As EventArgs) Handles btnreadScanner.Click
Dim thrReadScanner As New System.Threading.Thread(AddressOf ReadScanner)
thrReadScanner.IsBackground = True
thrReadScanner.Start()
End Sub
Private Sub ReadScanner()
Dim strRowCount As String
ShipmentMsg(2)
strRowCount = objShipping.RecordsExisit.ToString()
Try
objApex.ImmediateMode()
If objApex.FileDownload = False Then
Throw New Exception(Err.Description)
End If
Catch ex As Exception
ShipmentMsg(1)
MessageBox.Show("No Data downloaded from Scanner. Try Again. Error#: " & Err.Number & " : " & Err.Description)
Exit Sub
End Try
RecordCount()
DataGridUpdate()
btnProcessShipment.Enabled = True
ShipmentMsg(5)
ScanErrors()
End Sub
This all works great. As expected. The call to objApex.FileDownload in class cApex is where progress bar is to be updated from (actually in another function called from FileDownload). So here is the code there.
Try
GetHeaderRecord()
If Count <> 0 Then intTicks = Math.Round((100 / Count), 1)
For intcount As Integer = 1 To Count
Dim intLength As Integer = Length
Do While intLength > 0
literal = Chr(_serialPort.ReadChar.ToString)
If literal = ">" Then Exit Do
strRecord = strRecord & literal
intLength = intLength - 1
Loop
REF = strRecord.Substring(0, 16).TrimEnd
SKID = strRecord.Substring(16, 16).TrimEnd
REEL_BC = strRecord.Substring(32, 15).TrimEnd
ScanDate = strRecord.Substring(47, 8).TrimEnd
ScanDate = DateTime.ParseExact(ScanDate, "yyyyMMdd", Nothing).ToString("MM/dd/yyyy")
ScanTime = strRecord.Substring(55, 6).TrimEnd
ScanTime = DateTime.ParseExact(ScanTime, "HHmmss", Nothing).ToString("HH:mm:ss")
strRecordTotal = strRecordTotal & strRecord & CRLF
Dim strSQL As String
strSQL = "INSERT INTO tblScanData (PONo,Barcode,SkidNo,ScanDate,ScanTime) " & _
"VALUES (" & _
Chr(39) & REF & Chr(39) & _
"," & Chr(39) & REEL_BC & Chr(39) & _
"," & Chr(39) & SKID & Chr(39) & _
"," & Chr(39) & ScanDate & Chr(39) & _
"," & Chr(39) & ScanTime & Chr(39) & ")"
objData.Executecommand(strSQL)
strRecord = ""
Next
And finally this is how I was calling the progress bar update.
Dim f As frmMain = frmMain
System.Threading.Thread.Sleep(100)
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
I really need to put the PerformStep in the For loop. Each time around the loop will step the progress bar the percentage of steps needed to make bar fairly accurate (done by the math code before loop). Also I setup the properties of the progress bar control on frmMain. So, am I crazy, or is there a way to accomplish this? I tried using a delegate; Me.Invoke(New MethodInvoker(AddressOf pbStep)) to make code cross thread safe. I don't get an error about cross thread calls, but the progress bar doesn't update either. Sorry it's a long one but I'm lost and my ADHD won't let me scrap this idea.
EDIT AS REQUESTED:
Public Sub pbStep()
Dim f As frmMain = frmMain
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf pbStep))
Else
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
System.Threading.Thread.Sleep(100)
End If
End Sub
Both responses helped lead me to the correct answer I was needing. The code provided by James was a great starting point to build on, and Hans has several post explaining the BackgroundWorker. I wanted to share the "Answer" I came up with. I'm not saying its the best way to do this, and I'm sure I'm violating some rules of common logic. Also, a lot of the code came from a MSDN example and James's code.
Lets start with the form from which I am calling the bgw, frmShipping. I added this code:
Dim WithEvents bgw1 As New System.ComponentModel.BackgroundWorker
Private Sub bgw1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgw1.RunWorkerCompleted
If e.Error IsNot Nothing Then
MessageBox.Show("Error: " & e.Error.Message)
ElseIf e.Cancelled Then
MessageBox.Show("Process Canceled.")
Else
MessageBox.Show("Finished Process.")
End If
End Sub
Private Sub bgw1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
Handles bgw1.ProgressChanged
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Maximum = 1960
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Step = 2
Dim state As cApex.CurrentState =
CType(e.UserState, cApex.CurrentState)
DirectCast(Me.MdiParent, frmMain).txtCount.Text = state.LinesCounted.ToString
DirectCast(Me.MdiParent, frmMain).txtPercent.Text = e.ProgressPercentage.ToString
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.PerformStep()
End Sub
Private Sub bgw1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles bgw1.DoWork
Dim worker As System.ComponentModel.BackgroundWorker
worker = CType(sender, System.ComponentModel.BackgroundWorker)
Dim objApex As cApex = CType(e.Argument, cApex)
objApex.CountLines(worker, e)
End Sub
Sub StartThread()
Me.txtCount.Text = "0"
Dim objApex As New cApex
bgw1.WorkerReportsProgress = True
bgw1.RunWorkerAsync(objApex)
End Sub
Next I added the following code the my cApex class.
Public Class CurrentState
Public LinesCounted
End Class
Private LinesCounted As Integer = 0
Public Sub CountLines(ByVal worker As System.ComponentModel.BackgroundWorker, _
ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim state As New CurrentState
Dim line = ""
Dim elaspedTime = 20
Dim lastReportDateTime = Now
Dim lineCount = File.ReadAllLines(My.Settings.strGenFilePath).Length
Dim percent = Math.Round(100 / lineCount, 2)
Dim totaldone As Double = 2
Using myStream As New StreamReader(My.Settings.strGenFilePath)
Do While Not myStream.EndOfStream
If worker.CancellationPending Then
e.Cancel = True
Exit Do
Else
line = myStream.ReadLine
LinesCounted += 1
totaldone += percent
If Now > lastReportDateTime.AddMilliseconds(elaspedTime) Then
state.LinesCounted = LinesCounted
worker.ReportProgress(totaldone, state)
lastReportDateTime = Now
End If
System.Threading.Thread.Sleep(2)
End If
Loop
state.LinesCounted = LinesCounted
worker.ReportProgress(totaldone, state)
End Using
End Sub
I also added a couple of text boxes to my main form to show the current line count from the file being read from and the overall progress as a percentage of a 100. Then on the Click event of my button I just call StartThread(). It is not 100% accurate, but its close enough to give the user a very good idea where the process stands. I have a little more work to do to add it to the "ReadScanner" function, where I originally was wanting to use the progress bar. But this function it the longer of the two that I perform on the scanner, writing almost 2,000 lines of code through a COMM Port. I'm happy with the results.
Thank you guys for helping out!
P.S. I have also now added variables to set the pbar.Maximum and the pbar.step since those can change if the scanner file is changed.
Background workers are useful for this purpose. Just use it in combination with a delegate. All the threaded work is done within the DoWork event of the worker. As progress is made, progress is reported within the DoWork event. This in turn fires the ProgressedChanged event of the worker class which is on the same thread as the progressbar. Once the DoWork has completed and is out of scope, the RunWorkerCompleted event is fired. This can be used to do inform the user that the task is complete, etc. Here is a working solution that I threw together. Just paste it behind an empty form and run.
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Private _progressBar As ProgressBar
Private _worker As BackgroundWorker
Sub New()
' This call is required by the designer.
InitializeComponent()
Initialize()
BindComponent()
End Sub
Private Sub Initialize()
_progressBar = New ProgressBar()
_progressBar.Dock = DockStyle.Fill
_worker = New BackgroundWorker()
_worker.WorkerReportsProgress = True
_worker.WorkerSupportsCancellation = True
Me.Controls.Add(_progressBar)
End Sub
Private Sub BindComponent()
AddHandler _worker.ProgressChanged, AddressOf _worker_ProgressChanged
AddHandler _worker.RunWorkerCompleted, AddressOf _worker_RunWorkerCompleted
AddHandler _worker.DoWork, AddressOf _worker_DoWork
AddHandler Me.Load, AddressOf Form1_Load
End Sub
Private Sub Form1_Load()
_worker.RunWorkerAsync()
End Sub
Private Sub _worker_ProgressChanged(ByVal o As Object, ByVal e As ProgressChangedEventArgs)
_progressBar.Increment(e.ProgressPercentage)
End Sub
Private Sub _worker_RunWorkerCompleted(ByVal o As Object, ByVal e As RunWorkerCompletedEventArgs)
End Sub
Private Sub _worker_DoWork(ByVal o As Object, ByVal e As DoWorkEventArgs)
Dim worker = DirectCast(o, BackgroundWorker)
Dim value = 10000
SetProgressMaximum(value)
For x As Integer = 0 To value
Thread.Sleep(100)
worker.ReportProgress(x)
Next
End Sub
Private Sub SetProgressMaximum(ByVal max As Integer)
If _progressBar.InvokeRequired Then
_progressBar.Invoke(Sub() SetProgressMaximum(max))
Else
_progressBar.Maximum = max
End If
End Sub
End Class
This is my first time making a windows service app. I'm trying to move files from one folder to another using a windows service app. It'll do so every 10 seconds.
This is the code I'm using. It works when I use it on a windows form app but doesn't work when I use it on a windows-service app.
The code in the Timer1_Tick works if I use it in OnStart. But doesn't work in the timer.
Protected Overrides Sub OnStart(ByVal args() As String)
Timer1.Enabled = True
End Sub
Protected Overrides Sub OnStop()
Timer1.Enabled = False
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim FileToMove As String
Dim MoveLocation As String
Dim count1 As Integer = 0
Dim files() As String = Directory.GetFiles("C:\Documents and Settings\dmmc.operation\Desktop\Q8")
Dim pdfFiles(100) As String
For i = 0 To files.Length - 1
If Path.GetExtension(files(i)) = ".pdf" Then
pdfFiles(count1) = files(i)
count1 += 1
End If
Next
For i = 0 To pdfFiles.Length - 1
If pdfFiles(i) <> "" Then
FileToMove = pdfFiles(i)
MoveLocation = "C:\Documents and Settings\dmmc.operation\Desktop\Output\" + Path.GetFileName(pdfFiles(i))
If File.Exists(FileToMove) = True Then
File.Move(FileToMove, MoveLocation)
End If
End If
Next
End Sub
Windows.Forms.Timer won't work without a Form instantiated. You should be using System.Timers.Timer instead:
Private WithEvents m_timer As System.Timers.Timer
Protected Overrides Sub OnStart(ByVal args() As String)
m_timer = New System.Timers.Timer(1000) ' 1 second
m_timer.Enabled = True
End Sub
Private Sub m_timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles m_timer.Elapsed
m_timer.Enabled = False
'Do your stuff here
m_timer.Enabled = True
End Sub
I've also build a service that moves files that are dropped into a folder but I'm using the FileSystemWatcher. The FileSystemWatcher allows me to move the file when a new one is created, create an entry in a SQL Database and send an email notification when it's all done.
This is how I have setup the FileSystemWatcher
Set a folder to watch: watcher.Path = "C:\Folder to Watch"
Set the file type to watch: watcher.Filter = "*.pdf".
The type of event to watch and the Method that gets triggered: watcher.Created += new FileSystemEventHandler(OnChanged);
Lastly, I need to enable the event: watcher.EnableRaisingEvents = true;
Unfortunately, I have instances on a daily basis where the files do not get moved successfully. I get IO exceptions for file in use. The files get copied but the file in the destination folder is 0kb.
I'm trying to troubleshoot it and I've manage to debug remotely but I still haven't figured out what I am doing wrong.
The most common error I get is: Error: System.IO.IOException: The process cannot access the file 'fileName.pdf' because it is being used by another process.
this error does not make sense as the file did not exist prior to my service trying to move it...
Any further help would be appreciated.