Solved

BackgroundWorker implimentation assistance

Posted on 2008-10-05
7
546 Views
Last Modified: 2013-11-27
Hello Experts,

I have been working with BackgroundWorker in my latest project thus far.  What I want to do is to be able to process a Text Deliniated file based upon the input from the user.  The import process can take several minute depending on the size of the file so I want to be sure that the user's UI doesn't get messed up.

When the process starts the file, file type and deliniator are entered by the user.  When they click the Preview button the background task starts.  To keep the user informed I have a ProgressBar that will be updated as the data file is processed.  The Status text will indicate "Processing..." with the percentage and the bar gets updated accordingly.

I have successfully setup the application ti import the text fkle and place it into a DataTable that is used as the DataSource for the DataGridView in the foreground.  I think I have properly written the code to do this in the background, but the ProgressBar does not update with the current processing point.  The other problem is that the DataGridView doesn't display the DataTable.  Hmmmmm?????

So I need an extra pare of eyes to look at the attached code please.  So I have two basic questions:

1.  Could you look at the code and assist me with fixing the 'ProgressBar' updates and DataGridView update of the DataSource by the DataTable?

2. Is there a way I could setup the BackgroundWorker in a class that would accept a 'Process' to be done, update a ProgressBar and then return the results whem completed in a modular way?  This way I would not have to keep writing code do handle a backgroun process?

Thanks so very much for the assistance!!!
Imports Microsoft.VisualBasic.FileIO

Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Drawing

Imports System.IO

Imports System.Text

Imports System.Threading

Imports System.Windows.Forms
 
 

Public Class Form2

    Inherits System.Windows.Forms.Form
 

    Private WithEvents bgw_ImportDataFile As BackgroundWorker

    Private sFile As String = Nothing

    Private TmpTable As DataTable = Nothing
 
 
 

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 

        'Load table data

        Me.taDBStructureListDeliniator.Fill(Me.DsDatabaseStructureLists.tblDBStructureListDeliniator)

        Me.taDBStructureListFormatType.Fill(Me.DsDatabaseStructureLists.tblDBStructureListFormatType)
 

        'Set default Options and Values

        Me.TextBox_SourceFile.Text = "D:\Documents and Settings\Peter Allen\My Documents\Developement\Microsoft Access 2003\Integrated Components and Maintenance Tracking System\Table Data\AssetCenter\SEIDNAME.TXT"

        Me.ComboBox_FileFormat.Text = "DELINIATED"

        Me.ComboBox_FileDeliniator.Text = "Coma"

        Me.DataGridView1.Tag = "Default"

        Me.DataGridView2.Tag = "Default"

        Me.TabControl_FileSpecifications.SelectedTab = TabPage_FileOptions

        Me.StatusStrip_ProgressBar.Value = 0

        Me.StatusStrip_FileStatusText.Text = "Ready"

        Me.StatusStrip_ProgressLabel.Visible = False

        Me.StatusStrip_ProgressBar.Visible = False

        subStateButtons("Default")
 

        subDataGridView_Initialize()

        subDataGridView_Header()

        subDataGridView_DataRow()
 

    End Sub
 

    Private Sub subDataGridView_Initialize()
 

        Select Case Me.DataGridView1.Tag

            Case "Default"

                With Me.DataGridView1

                    .AllowUserToAddRows = False

                    .AllowUserToDeleteRows = False

                    .AllowUserToResizeRows = False

                    .SelectionMode = DataGridViewSelectionMode.FullRowSelect

                    .MultiSelect = False

                    .Dock = DockStyle.None

                End With

        End Select
 

        Select Case Me.DataGridView2.Tag

            Case "Default"

                With Me.DataGridView2

                    .ColumnCount = 3

                    .RowCount = 2

                    .AllowUserToAddRows = False

                    .AllowUserToDeleteRows = False

                    .AllowUserToResizeRows = False

                    .SelectionMode = DataGridViewSelectionMode.FullRowSelect

                    .MultiSelect = False

                    .Dock = DockStyle.None

                End With

        End Select
 

    End Sub
 

    Private Sub subDataGridView_Header()
 

        'DataGridView Heading Style

        With DataGridView2.ColumnHeadersDefaultCellStyle

            .BackColor = Color.Navy

            .ForeColor = Color.White

            .Font = New Font(DataGridView2.Font, FontStyle.Bold)

        End With
 

        'Column Heading Style Attributes

        With DataGridView2

            .Name = "DataGridView1"

            .AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCellsExceptHeaders

            .CellBorderStyle = DataGridViewCellBorderStyle.Single

            .ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single

            .GridColor = Color.Black

            .RowHeadersVisible = False

        End With
 

        'Column Heading Names

        Me.DataGridView2.Columns(0).Name = "Field Name"

        Me.DataGridView2.Columns(1).Name = "Field Type"

        Me.DataGridView2.Columns(2).Name = "Include"

    End Sub

    
 

    Private Sub subDataGridView_DataRow()
 

        Dim iRow As Integer

        Dim iCol As Integer
 

        'Set the Column data

        For iCol = 1 To Me.DataGridView2.ColumnCount

            For iRow = 1 To Me.DataGridView2.RowCount

                Me.DataGridView2.Item(iCol - 1, iRow - 1).Value = "Field"

            Next iRow

        Next iCol
 

    End Sub
 

    <STAThreadAttribute()> _

    Public Shared Sub Main()

        Application.EnableVisualStyles()

        Application.Run(New Form2())
 

    End Sub
 

    Private Sub Button_Preview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_Preview.Click
 

        Dim o_fncImportDataTable_Processer As New clsDataAccess_ImportDataFile

        Dim bgw_ImportDataFile = New System.ComponentModel.BackgroundWorker
 

        sFile = Me.TextBox_SourceFile.Text

        Me.TabControl_FileSpecifications.SelectedTab = TabPage_FileView

        Me.StatusStrip_ProgressLabel.Visible = True

        Me.StatusStrip_ProgressBar.Visible = True

        subStateButtons("Default")
 

        'Update Tab: View

        Me.DataGridView1.DataSource = ""

        Me.StatusStrip_FileStatusText.Text = "Processing..."

        Me.Refresh()
 

        o_fncImportDataTable_Processer.fncImportDataTable_RowCount(sFile)

        Me.StatusStrip_ProgressBar.Maximum = 100

        Me.StatusStrip_ProgressBar.Minimum = 0

        Me.StatusStrip_ProgressBar.Step = 10

        Me.TextBox_RowCount.Text = Str(o_fncImportDataTable_Processer.p_RowCount)

        Me.TextBox_FieldCount.Text = Str(o_fncImportDataTable_Processer.p_ColCount)
 

        bgw_ImportDataFile.WorkerReportsProgress = True

        bgw_ImportDataFile.WorkerSupportsCancellation = True

        bgw_ImportDataFile.RunWorkerAsync(TmpTable)
 

        'Me.DataGridView1.DataSource = o_fncImportDataTable_Processer.fncImportDataTable_Processer(sFile)

        For iCol As Integer = 1 To DataGridView1.ColumnCount

            Me.DataGridView1.AutoResizeColumn(iCol - 1)

            Me.DataGridView1.Columns(iCol - 1).ReadOnly = True

        Next iCol
 

        'Update Tab: Structure

        Me.DataGridView2.ColumnCount = 3

        Me.DataGridView2.RowCount = o_fncImportDataTable_Processer.p_ColCount + 1

        subDataGridView_Header()

        subDataGridView_DataRow()
 

        Me.StatusStrip_FileStatusText.Text = "Ready"

        Me.Refresh()
 

    End Sub
 

    Private Sub Button_Close_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_Close.Click
 

        Me.Close()
 

    End Sub
 

    Private Sub subStateButtons(ByVal sAction As String)
 

        If Me.TextBox_SourceFile.TextLength > 0 Then

            Me.Button_Preview.Enabled = True

        Else

            Me.Button_Preview.Enabled = False

        End If
 

    End Sub
 

    Private Sub bgw_ImportDataFile_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw_ImportDataFile.DoWork
 

        TmpTable = DirectCast(e.Argument, DataTable)

        Dim o_fncImportDataTable_Processer As New clsDataAccess_ImportDataFile

        TmpTable = o_fncImportDataTable_Processer.fncImportDataTable_Processer(sFile)

        e.Result = TmpTable

        'For Value As Integer = 0 To 100

        'If bgw_ImportDataFile.CancellationPending Then

        'Exit For

        'End If

        'bgw_ImportDataFile.ReportProgress(Value, ListText)

        'Threading.Thread.Sleep(100)

        'Next Value
 

    End Sub
 

    Private Sub bgw_ImportDataFile_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgw_ImportDataFile.ProgressChanged
 

        StatusStrip_ProgressBar.Value = e.ProgressPercentage
 

    End Sub
 

    Private Sub Button_Cancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_Cancel.Click
 

        bgw_ImportDataFile.CancelAsync()
 

    End Sub
 

    Private Sub bgw_ImportDataFile_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgw_ImportDataFile.RunWorkerCompleted
 

        Me.DataGridView1.DataSource = e.Result

        Me.StatusStrip_ProgressLabel.Visible = False

        Me.StatusStrip_ProgressBar.Visible = False

        Me.StatusStrip_ProgressBar.Value = 0
 

    End Sub
 

End Class
 

Public Class clsDataAccess_ImportDataFile
 

    Dim _RowCount As Integer

    Dim _ColCount As Integer
 

    Public Property p_RowCount() As Integer

        Get

            Return _RowCount

        End Get

        Set(ByVal value As Integer)

            _RowCount = value

        End Set

    End Property
 

    Public Property p_ColCount() As Integer

        Get

            Return _ColCount

        End Get

        Set(ByVal value As Integer)

            _ColCount = value

        End Set

    End Property
 
 

    Public Function fncImportDataTable_Processer(ByVal FileName As String) As DataTable
 

        ' Initialize the return values

        Dim list As New List(Of String())

        Dim table As DataTable = Nothing

        Using parser As New TextFieldParser(FileName)

            ' Setup the comma-delimited file parser.

            parser.TextFieldType = FieldType.Delimited

            subImportDataTable_Deliniator(parser)

            parser.HasFieldsEnclosedInQuotes = False

            While Not parser.EndOfData

                Try

                    ' Read the comma-delimited text as fields into a string array.

                    Dim input As String() = parser.ReadFields()

                    If table Is Nothing Then

                        table = Me.fncImportDataTable_Headings(Path.GetFileName(FileName), input)

                    End If

                    If input(0).Trim <> "" And input(0) <> "******" Then

                        Me.fncImportDataTable_DataRow(table, input)

                        'Form2.StatusStrip_ProgressBar.PerformStep()

                    Else

                        'Else send to Error table

                    End If

                Catch ex As MalformedLineException

                    ' Ignore invalid lines.

                End Try

            End While

        End Using

        Return table
 

    End Function
 

    Private Function fncImportDataTable_Headings(ByVal name As String, ByVal input As String()) As DataTable
 

        Dim table As New DataTable(name)

        For index As Integer = 1 To input.Length

            table.Columns.Add("Field" & index)

        Next index

        Return table
 

    End Function
 

    Private Sub fncImportDataTable_DataRow(ByVal table As DataTable, ByVal input As String())
 

        Dim row As DataRow = table.NewRow()

        For index As Integer = 0 To table.Columns.Count - 1

            If index < input.Length Then

                row(index) = input(index)

            End If

        Next index

        table.Rows.Add(row)
 

    End Sub
 

    Public Function fncImportDataTable_RowCount(ByVal Filename As String) As Integer
 

        ' Initialize the return values

        Dim list As New List(Of String())

        Dim table As DataTable = Nothing

        Dim iRows As Integer = 0

        Dim iCols As Integer = 0

        Using parser As New TextFieldParser(Filename)

            ' Setup the comma-delimited file parser.

            parser.TextFieldType = FieldType.Delimited

            subImportDataTable_Deliniator(parser)

            parser.HasFieldsEnclosedInQuotes = False

            While Not parser.EndOfData

                Try

                    ' Read the comma-delimited text as fields into a string array.

                    Dim input As String() = parser.ReadFields()

                    iRows = iRows + 1

                    If iRows = 1 Then

                        iCols = parser.ReadFields.Count

                    End If

                Catch ex As MalformedLineException

                    ' Ignore invalid lines.

                End Try

            End While

        End Using

        p_RowCount = iRows

        p_ColCount = iCols
 

    End Function
 

    Private Sub subImportDataTable_Deliniator(ByRef parser)
 

        Select Case Form2.ComboBox_FileDeliniator.Text

            Case "Coma"

                parser.Delimiters = New String() {","}

            Case "Semi-Colin"

                parser.Delimiters = New String() {";"}

        End Select

    End Sub

    

End Class

Open in new window

0
Comment
Question by:Peter Allen
  • 4
  • 3
7 Comments
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 22647058
The problem is right here:

    Private Sub Button_Preview_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_Preview.Click
 
        ...
        Dim bgw_ImportDataFile = New System.ComponentModel.BackgroundWorker

By placing "Dim" in front of "bgw_ImportDataFile" you have created a LOCAL variable in Button_Preview_Click().  This local variable is actually HIDING the previously declared variable you had placed at the Form level.  The Form level BackgroundWorker variable never actually gets used!  This is why it never traps any of the events...because it doesn't point to anything....

Removing the "Dim" should fix the problem:

        bgw_ImportDataFile = New System.ComponentModel.BackgroundWorker

0
 

Author Comment

by:Peter Allen
ID: 22650734
Yes.  Darn that one word!  Thank you for that.  I ran the code and I was able to get further.  Now I am looking at the Delimiters procedure.  I had referenced a control on the form to determine what the Delimiter was.  This was not getting passed to the BackgroundWorker.  So I added a new private string called SDelimiter which does pass the text to determine the proper deliniation character.

In the following sub-routine...

    Private Sub bgw_ImportDataFile_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgw_ImportDataFile.RunWorkerCompleted

        Me.DataGridView1.DataSource = TmpTable
        Me.StatusStrip_ProgressLabel.Visible = False
        Me.StatusStrip_ProgressBar.Visible = False
        Me.StatusStrip_ProgressBar.Value = 0

    End Sub

The DataGridView1.DataSource was changed to accept the correct DataTable instead of e.Result.  My goofup.  The process works very well, Thank you for the solution.  I do have those two additional questions for you, though.

What about how to report the progress of fncImportDataTable_Processer?

I know that I have to call ProgressChanged, but am unclear as to how to report a percentage back to the UI to a ProgressBar.

And what about the ability to set up a BackgroundWorker in such a way as to be able to call it to perform these actions without writing it more then one time.  Let's say in a class.

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 22651456
"What about how to report the progress of fncImportDataTable_Processer?"

Well...since you have put the "meat" into a separate class you would have to do either one of two things:

(a) Pass the BackgroundWorker into the Class so that it can call the ReportProgess() method directly.
(b) Make the clsDataAccess_ImportDataFile Class Raise its own Progress events that the BackgroundWorker subscribes to.  Then it will relay that Progress by raising its own Progress event in turn.

To get the actual progress you need to base your TextFieldParser on a Stream instead of passing it a FileName directly.  This way you can query the underlying stream to determine how far along it is compared to the total length of the file.  This approach isn't perfect, though, because .Net Streams actually read ahead and buffer the data for you...so it may say 100% a little before it is actually done.

Here is a small snippet that demonstrates the concept:
    Private Sub Button1_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim fs As New FileStream("c:\someFile.txt", FileMode.Open)

        Dim tfp As New TextFieldParser(fs)

        tfp.Delimiters = New String() {","}

        Dim values() As String

        Dim percent As Decimal

        While Not tfp.EndOfData

            values = tfp.ReadFields

            percent = fs.Position / fs.Length * 100
 

            Label1.Text = percent.ToString("0") & "%"

            Application.DoEvents()
 

        End While

        tfp.Close()
 

        MessageBox.Show("Done")

    End Sub

Open in new window

0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Author Comment

by:Peter Allen
ID: 22653306
I was looking at the code ip point B, but I think Point A is a better solution.  I am stuck, though on how to pass the function into the class.   ...

Thank you so much for the assistance.
0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
ID: 22653460
Ok...first, in your Class, add a variable to hold the BackgroundWorker reference.  Then modify the Constructor to accept the external parameter and assign it to the internal variable:

    Public Class clsDataAccess_ImportDataFile

        ... rest of your already existing class ...

        Private bgw As System.ComponentModel.BackgroundWorker = Nothing

        Public Sub New(ByVal bgw As System.ComponentModel.BackgroundWorker)
            Me.bgw = bgw
        End Sub

        ... rest of your already existing class ...

    End Class

Then, in the "DoWork" method you would pass in the BackgroundWorker control when you create your class:

    Private Sub bgw_ImportDataFile_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw_ImportDataFile.DoWork
        TmpTable = DirectCast(e.Argument, DataTable)
        Dim o_fncImportDataTable_Processer As New clsDataAccess_ImportDataFile(Me.bgw_ImportDataFile)

Finally, in your class you use your reference to report progress:

    Public Class clsDataAccess_ImportDataFile

        ... rest of your already existing class ...

        Private bgw As System.ComponentModel.BackgroundWorker = Nothing

        Public Sub New(ByVal bgw As System.ComponentModel.BackgroundWorker)
            Me.bgw = bgw
        End Sub

        Public Function fncImportDataTable_Processer(ByVal FileName As String) As DataTable

            ... rest of your already existing function ...

            bgw.ReportProgress(someValue / someTotal * 100)

            ... rest of your already existing function ...

        End Function

        ... rest of your already existing class ...

    End Class
0
 

Author Comment

by:Peter Allen
ID: 22653833
I will get back with you on thos becaise pne pf the values I need is not getting inserted.  I will let you know tomorrow.  Thank you so very much!
0
 

Author Closing Comment

by:Peter Allen
ID: 31503229
Idle Mind,

I accept your solution   THANK YOU!!  The solution works perfectly.  For the percentage I calculated the row count in the text file first before the actual import.  The ProgressBar updates as it should now.  I just had to add 1 to the row count to preventing an unhandled exception.

Thanks again!
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Suggested Solutions

One of the most frequent problems a "newbie" developer may encounter is having to deal with different data formats. One for all: THE DATE We, as humans, need to "see" a date and then interpret it (much of the times this is an automatic operation)…
"Disruption" is the most feared word for C-level executives these days. They agonize over their industry being disturbed by another player - most likely by startups.
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now