长期读者,第一次海报。通常我能找到答案并使其有效。不是这次.....我在VS2013中使用VB.NET。我正在尝试使用辅助线程中的工作更新进度条。容易吗?不,我必须让它变得更复杂。进度条( ToolStripProgressBar1 )位于主窗体( frmMain )上,即项目的MDI。辅助表单( frmShipping )有一个按钮,用于启动第二个线程以在类中执行某些COMM端口通信( cApex )。我可以从主UI线程( frmShipping 按钮)获取进度条以更新 frmMain 。
这是来自 frmShiping 按钮的代码和多线程程序:
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
这一切都很棒。正如所料。类 cApex 中对 objApex.FileDownload 的调用是更新进度条的位置(实际上是从FileDownload调用的另一个函数)。所以这里是代码。
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
最后这就是我调用进度条更新的方式。
Dim f As frmMain = frmMain
System.Threading.Thread.Sleep(100)
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
我真的需要将PerformStep放在For循环中。每次循环都会使进度条步进使条形相当准确所需的步数百分比(在循环之前由数学代码完成)。我还在 frmMain 上设置了进度条控件的属性。那么,我是疯了,还是有办法实现这一目标?我尝试过使用代理人; Me.Invoke(New MethodInvoker(AddressOf pbStep))使代码跨线程安全。我没有得到关于跨线程调用的错误,但进度条也没有更新。对不起,这是一个很长的,但我迷路了,我的多动症不会让我废弃这个想法。
根据要求编辑:
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
答案 0 :(得分:2)
这两个回复都帮助我找到了我需要的正确答案。 James提供的代码是一个很好的起点,Hans有几篇文章解释了BackgroundWorker。我想分享一下我提出的“答案”。我并不是说这是最好的方法,而且我确定我违反了一些共同逻辑规则。此外,许多代码来自MSDN示例和James的代码。
让我从我调用bgw,frmShipping的表单开始。我添加了这段代码:
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
接下来,我将以下代码添加到我的cApex类中。
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
我还在我的主窗体中添加了几个文本框,以显示正在读取的文件中的当前行数和总体进度百分比。然后在我的按钮的Click事件中,我只是调用{ {1}}。它不是100%准确,但足够接近,可以让用户非常了解流程的位置。我还有一些工作要做,将它添加到“ReadScanner”函数中,我最初想要使用进度条。但是这个功能是我在扫描仪上执行的两个中的较长时间,通过COMM端口写入近2,000行代码。我对结果感到满意。
谢谢大家的帮助!
P.S。我现在还添加了变量来设置StartThread()
和pbar.Maximum
,因为如果扫描程序文件发生了变化,这些变量就会发生变化。
答案 1 :(得分:0)
后台工作人员可用于此目的。只需将其与代表一起使用即可。所有线程工作都在worker的DoWork事件中完成。随着进展,在DoWork事件中报告进度。这反过来触发worker类的ProgressedChanged事件,该事件与进度条位于同一个线程上。 DoWork完成并超出范围后,将触发RunWorkerCompleted事件。这可以用来告知用户任务完成等等。这是一个工作解决方案,我把它放在一起。只需将其粘贴在空表格后面即可运行。
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