Link to home
Start Free TrialLog in
Avatar of gbarcalow
gbarcalow

asked on

Changing textbox from a thread. VB.Net

I cant change the text in a label or textbox from a thread.

Description of what I have.
I have two classes "VIPList", and another Public Class "BackgroundQuery"

When the thread has completed it sends back 2 strings using a public event. if there was no error this process works 100% great. If there is an error the event handler in "VIPList" is supposed to change the textbox text to one of the 2 strings passed from the thread. (instead of being dumped to a txt file) My problem is the text gets passed but not set. The textbox always ends up empty.

Any Ideas?
Code available if necessary.
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

>> Code available if necessary.

We need to see your code.  The interaction you describe is complex and the problem could be anywhere in the process.

=)
Idle_Mind
Avatar of gbarcalow
gbarcalow

ASKER

Public Class VIPList
    Inherits System.Windows.Forms.Form
    ...
    Private WithEvents oBGProcess As BackgroundQuery
    Private tMain As Thread

    'button click starts thread

    Private Sub QueryDone(ByVal sDateFrom As String, ByVal sDateTo As String) Handles oBGProcess.eQueryComplete
    ....
    If sDateFrom = "Error" Then
            'This is the code that doesnt work.
            txtError.Visible = True
            txtError.Text = "ERROR: " & vbCrLf & sDateTo
    Else
            'This code works fine
            SaveSetting("CSRT", User_ID, "VIP Estimated", tTimeStarted & "~" & Now)
            ProgressBar1.Value = ProgressBar1.Maximum

            Dim proc As Process
            If File.Exists(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv") Then _
            proc.Start(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv")
    End If  

    End Sub
End Class

Public Class BackgroundQuery

    Public Event eQueryComplete(ByVal sDateFrom As String, ByVal sDateTo As String)

    ....

    Public Sub MainQuery()
        Dim GBAScm As New OleDb.OleDbCommand
        Dim GBASdr As OleDb.OleDbDataReader

        'Go do it
        Try
            GBAScn.Open()
            GBAScm.Connection = GBAScn

            'Query Here

            'Write Query Results to text file here

        Catch ex As Exception
            RaiseEvent eQueryComplete("Error", ex.Message)
        Finally
            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
            RaiseEvent eQueryComplete(sDateFrom, sDateTo)
        End Try
    End Sub

End Class
The Finally block is always run, regardless of any actions occurring in preceding Catch blocks so I think what is happening is your error event is being raised but you are receiving another one immediately after that from the finally block.  Since there was error in the query, the values will be empty.

Try it the way I have it below instead...

Regards,

Idle_Mind

    Public Sub MainQuery()
        Dim GBAScm As New OleDb.OleDbCommand
        Dim GBASdr As OleDb.OleDbDataReader

        'Go do it
        Try
            GBAScn.Open()
            GBAScm.Connection = GBAScn

            'Query Here

            'Write Query Results to text file here

            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
            RaiseEvent eQueryComplete(sDateFrom, sDateTo)

        Catch ex As Exception
            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
            RaiseEvent eQueryComplete("Error", ex.Message)

        End Try
    End Sub
Yeah, it still has the same results. (already tried that)
When in debug mode I cant step through eQueryComplete and I see that sdateto has a value of the error text, and the code that sets the textbox text gets run but when checking the text property immediatly after the code executes reveals that the text property is still "".
Just for grits and shins...why don't you try using a seperate event for errors and see what happens...

Public Class VIPList
    Inherits System.Windows.Forms.Form
    ...
    Private WithEvents oBGProcess As BackgroundQuery
    Private tMain As Thread

    'button click starts thread

    Private Sub QueryDone(ByVal sDateFrom As String, ByVal sDateTo As String) Handles oBGProcess.eQueryComplete
        ....
        SaveSetting("CSRT", User_ID, "VIP Estimated", tTimeStarted & "~" & Now)
        ProgressBar1.Value = ProgressBar1.Maximum

        Dim proc As Process
        If File.Exists(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv") Then _
        proc.Start(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv")
    End Sub

    Private Sub QueryError(ByVal sMsg As String) Handles oBGProcess.eQueryError
        txtError.Visible = True
        txtError.Text = "ERROR: " & vbCrLf & sMsg
    End Sub
End Class

Public Class BackgroundQuery
    Public Event eQueryComplete(ByVal sDateFrom As String, ByVal sDateTo As String)
    Public Event eQueryError(ByVal sMsg As String)
    ....
    Public Sub MainQuery()
        Dim GBAScm As New OleDb.OleDbCommand
        Dim GBASdr As OleDb.OleDbDataReader

        'Go do it
        Try
            GBAScn.Open()
            GBAScm.Connection = GBAScn

            'Query Here

            'Write Query Results to text file here
            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
            RaiseEvent eQueryComplete(sDateFrom, sDateTo)

        Catch ex As Exception
            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
            RaiseEvent eQueryError(ex.Message)

        End Try
    End Sub
End Class
When I do that, I cant step through the code and the textbox takes the value of the passed string but as soon at the thread finishes the textbox goes back to ""
Sorry, skip last post

When I do that, and when I step through the code the textbox takes the value of the passed string but as soon at the thread finishes the textbox goes back to ""
It sound to me like the thread and events are working just fine.  The problem most likely lies somewhere else where you are resetting the textbox and that sub/function is being called when you are not expecting it to.

Is it possible to a more complete picture of the code?

Idle_mind
Imports System.IO
Imports System.Threading

Public Class VIPList
    Inherits System.Windows.Forms.Form



    'THREADING
    Private WithEvents oBGProcess As BackgroundQuery  'Opens a new process for running the refresh thread
    Private tMain As Thread                           'main thread used to capture data from db

    Dim tTimeStarted As DateTime
    Dim dMiliSecondCounter As Decimal
    Dim sTemp As String

    Private Sub VIPList_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        txtCIUserName.Text = GetSetting("CSRT", User_ID, "Import Username", "")
        txtCIPassword.Focus()
        GetEstimatedTime()
        SplashForm.Hide()
    End Sub
    Private Sub btnCIImport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCIImport.Click

        If txtCIPassword.Text = "" Then MessageBox.Show("You must enter a password first", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) : txtCIPassword.Focus() : Exit Sub

        'Prep Work
        GetEstimatedTime()

        btnCIImport.Enabled = False
        btnCIImport.Text = "Searching Please Wait"
        tTimeStarted = Now
        tmrCounter.Enabled = True
        picbox.Visible = True
        Me.Cursor = Windows.Forms.Cursors.WaitCursor

        Application.DoEvents()

        Dim sDateFrom As String = dtpFrom.Value.Year & "-" & dtpFrom.Value.Month & "-" & dtpFrom.Value.Day
        Dim sDateTo As String = dtpTo.Value.Year & "-" & dtpTo.Value.Month & "-" & dtpTo.Value.Day

        Application.DoEvents()

        'Start Search Thread
        oBGProcess = New BackgroundQuery
        If Not tMain Is Nothing Then If tMain.ThreadState <> ThreadState.Stopped Then Exit Sub
        tMain = New Thread(AddressOf oBGProcess.MainQuery)
        Dim GBAScn As New OleDb.OleDbConnection
        Init_GBAS_Database_Connection(GBAScn, txtCIUserName.Text, txtCIPassword.Text)
        oBGProcess.GBAScn = GBAScn
        oBGProcess.sDateFrom = sDateFrom
        oBGProcess.sDateTo = sDateTo
        oBGProcess.sSearchFor = txtCISearchString.Text
        tMain.Start()
    End Sub
    Private Sub GetEstimatedTime()
        Dim sEstimated As String = GetSetting("CSRT", User_ID, "VIP Estimated", "15:00:00")

        If sEstimated = "15:00:00" Then
            lblEstimated2.Text = sEstimated
            ProgressBar1.Maximum = 900
        Else
            Dim sStart As String = Strings.Left(sEstimated, InStr(sEstimated, "~") - 1)
            Dim sEnd As String = Strings.Right(sEstimated, Len(sEstimated) - InStr(sEstimated, "~"))
            If CDate(sEnd).Subtract(CDate(sStart)).Minutes < 10 Then
                lblEstimated2.Text = "0" & CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Minutes & ":"
            Else
                lblEstimated2.Text = CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Minutes & ":"
            End If
            If CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Seconds < 10 Then
                lblEstimated2.Text += "0" & CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Seconds & ":00"
            Else
                lblEstimated2.Text += CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Seconds & ":00"
            End If
            ProgressBar1.Maximum = (CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Minutes * 60) + CDate(sEnd).AddSeconds(30).Subtract(CDate(sStart)).Seconds
        End If

    End Sub
    Private Sub QueryDone(ByVal sDateFrom As String, ByVal sDateTo As String) Handles oBGProcess.eQueryComplete
        btnCIImport.Enabled = True
        btnCIImport.Text = "Get List"
        picbox.Visible = False
        tmrCounter.Enabled = False
        Me.Cursor = Windows.Forms.Cursors.Arrow

        If sDateFrom = "Error" Then
            txtError.Visible = True
            txtError.Text = "ERROR: " & vbCrLf & sDateTo
            MessageBox.Show(sDateTo)
            'GenericError("VIP List", "Main Query", sDateTo)
        Else
            SaveSetting("CSRT", User_ID, "VIP Estimated", tTimeStarted & "~" & Now)
            ProgressBar1.Value = ProgressBar1.Maximum

            Dim proc As Process
            If File.Exists(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv") Then _
            proc.Start(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv")
        End If
        Application.DoEvents()
        Me.Activate()
    End Sub
    Private Sub Queryerror(ByVal sError As String) Handles oBGProcess.eQueryError
        txtError.Visible = True
        txtError.Text = "ERROR: " & vbCrLf & sError
    End Sub

    Private Sub tmrCounter_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrCounter.Tick
        dMiliSecondCounter += 1
        If dMiliSecondCounter = 100 Then dMiliSecondCounter = 0
        Dim sMinutes As String = Now.Subtract(tTimeStarted).Minutes.ToString
        Dim sSeconds As String = Now.Subtract(tTimeStarted).Seconds
        Dim sMiliSeconds As String = dMiliSecondCounter
        If CInt(sMinutes) < 10 Then sMinutes = "0" & sMinutes
        If CInt(sSeconds) < 10 Then sSeconds = "0" & sSeconds
        If CInt(sMiliSeconds) < 10 Then sMiliSeconds = "0" & sMiliSeconds

        lblElapsed2.Text = sMinutes & ":" & sSeconds & ":" & sMiliSeconds

        If (Now.Subtract(tTimeStarted).Seconds + Now.Subtract(tTimeStarted).Minutes * 60) < ProgressBar1.Maximum Then _
            ProgressBar1.Value = (Now.Subtract(tTimeStarted).Seconds + Now.Subtract(tTimeStarted).Minutes * 60)
    End Sub
End Class

Public Class BackgroundQuery

    Public Event eQueryComplete(ByVal sDateFrom As String, ByVal sDateTo As String)
    Public Event eQueryError(ByVal sError As String)

    Public GBAScn As New OleDb.OleDbConnection
    Public sDateFrom, sDateTo As String
    Public sSearchFor As String

    Public Sub MainQuery()
        Dim GBAScm As New OleDb.OleDbCommand
        Dim GBASdr As OleDb.OleDbDataReader

        Const sQuote As String = """"

        'Go do it
        Try
            GBAScn.Open()
            GBAScm.Connection = GBAScn

            GBAScm.CommandText = "SELECT CLMFIL_FILE.CASE_NUM, CLMFIL_FILE.CERT_NUM, CLMFIL_FILE.CLMT_NUM, DTLFIL_FILE.CLAIM_NUM, CLMFIL_FILE.LAST_NAME, CLMFIL_FILE.FIRST_NAME, DTLFIL_FILE.STATUS, DTLFIL_FILE.SYS_YYYYMMDD " & _
                                 "FROM CLMFIL_FILE, DTLFIL_FILE " & _
                                 "WHERE CLMFIL_FILE.MESSAGE_25 LIKE '%" & sSearchFor & "%' " & _
                                 "AND DTLFIL_FILE.CASE_NUM = CLMFIL_FILE.CASE_NUM " & _
                                 "AND DTLFIL_FILE.CERT_NUM = CLMFIL_FILE.CERT_NUM " & _
                                 "AND DTLFIL_FILE.CLMT_NUM = CLMFIL_FILE.CLMT_NUM " & _
                                 "AND DTLFIL_FILE.SYS_YYYYMMDD >= '" & sDateFrom & "' " & _
                                 "AND DTLFIL_FILE.SYS_YYYYMMDD <= '" & sDateTo & "' " & _
                                 "AND CLMFIL_FILE.STATUS='A' " & _
                                 "ORDER BY DTLFIL_FILE.SYS_YYYYMMDD ASC"
            GBASdr = GBAScm.ExecuteReader

            Dim sw As StreamWriter
            Dim F As File
            sw = F.CreateText(Application.StartupPath & "\" & sDateFrom & "_" & sDateTo & ".csv")
            sw.WriteLine(sQuote & "VIP Claim list" & sQuote)
            sw.WriteLine(sQuote & sDateFrom & " to " & sDateTo & sQuote)
            sw.WriteLine("""CASE"",""CERT"",""CLAIMANT"",""CLAIM"",""LAST NAME"",""FIRST NAME"",""STATUS"",""LAST ACTION DATE""")
            While GBASdr.Read
                sw.WriteLine(sQuote & GBASdr(0) & sQuote & "," & _
                             sQuote & GBASdr(1) & sQuote & "," & _
                             sQuote & GBASdr(2) & sQuote & "," & _
                             sQuote & GBASdr(3) & sQuote & "," & _
                             sQuote & GBASdr(4) & sQuote & "," & _
                             sQuote & GBASdr(5) & sQuote & "," & _
                             sQuote & GBASdr(6) & sQuote & "," & _
                             sQuote & GBASdr(7) & sQuote)
            End While
            sw.Close()
            RaiseEvent eQueryComplete(sDateFrom, sDateTo)
        Catch ex As Exception
            'RaiseEvent eQueryComplete("Error", ex.Message)
            RaiseEvent eQueryError(ex.Message)
        Finally
            If Not dr.IsClosed Then dr.Close()
            GBAScn.Close()
        End Try
    End Sub
End Class
I can't find anything wrong with your code.

=(
Idle_Mind
Comming from you I will take that as a compliment... LoL

Is there something in .net that wont allow one thread to change the properties of an object in another thread? I know that even though the QueryDone/QueryError code is located in the class of "VIPList" it is actually executed under the tMain thread.

Thats all I can come up with though.
I will make a test app to test the theory.

Idle_Mind
>> Is there something in .net that wont allow one thread to change the properties of an object in another thread?

Here is an app that is doing essentially the same thing as yours.  The class has two threads that raise different events.  The events are trapped by the main form and the label and progressbar are changed as a result.

Idle_Mind

Imports System.Threading
Public Class Class1

    Public Event posChange(ByVal bc As Color, ByVal deltaValue As Byte, ByVal msg As String)
    Public Event negChange(ByVal bc As Color, ByVal deltaValue As Byte, ByVal msg As String)

    Private posThread As Thread = New Thread(AddressOf Me.positiveProgess)
    Private negThread As Thread = New Thread(AddressOf Me.negativeProgress)

    Public Sub New()
        posThread.Start()
        negThread.Start()
    End Sub

    Private Sub positiveProgess()
        While True
            If DateTime.Now.Second Mod 2 = 0 Then
                RaiseEvent posChange(Color.Green, 1, "Positive")
            End If
            Thread.Sleep(50)
            Application.DoEvents()
        End While
    End Sub

    Private Sub negativeProgress()
        While True
            If DateTime.Now.Second Mod 2 <> 0 Then
                RaiseEvent negChange(Color.Red, 1, "Negative")
            End If
            Thread.Sleep(50)
            Application.DoEvents()
        End While
    End Sub

    Public Sub Dispose()
        posThread.Abort()
        negThread.Abort()
    End Sub

    Protected Overrides Sub Finalize()
        Dispose()
        MyBase.Finalize()
    End Sub

End Class


Imports System.Threading

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    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.
    Friend WithEvents ProgressBar1 As System.Windows.Forms.ProgressBar
    Friend WithEvents Label1 As System.Windows.Forms.Label
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.ProgressBar1 = New System.Windows.Forms.ProgressBar
        Me.Label1 = New System.Windows.Forms.Label
        Me.SuspendLayout()
        '
        'ProgressBar1
        '
        Me.ProgressBar1.Anchor = CType(((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.ProgressBar1.Location = New System.Drawing.Point(8, 40)
        Me.ProgressBar1.Name = "ProgressBar1"
        Me.ProgressBar1.Size = New System.Drawing.Size(330, 18)
        Me.ProgressBar1.TabIndex = 3
        Me.ProgressBar1.Value = 50
        '
        'Label1
        '
        Me.Label1.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
                    Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.Label1.BackColor = System.Drawing.Color.Black
        Me.Label1.ForeColor = System.Drawing.Color.White
        Me.Label1.Location = New System.Drawing.Point(8, 8)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(330, 24)
        Me.Label1.TabIndex = 5
        Me.Label1.Text = "Label1"
        Me.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(346, 64)
        Me.Controls.Add(Me.Label1)
        Me.Controls.Add(Me.ProgressBar1)
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
        Me.Name = "Form1"
        Me.Text = "Threads in a Class Raising Events"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private WithEvents c As Class1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        c = New Class1
    End Sub

    Private Sub c_posChange(ByVal bc As System.Drawing.Color, ByVal deltaValue As Byte, ByVal msg As String) Handles c.posChange
        Label1.BackColor = bc
        Label1.Text = msg
        If ProgressBar1.Value + deltaValue > ProgressBar1.Maximum Then
            ProgressBar1.Value = ProgressBar1.Maximum
        Else
            ProgressBar1.Value = ProgressBar1.Value + deltaValue
        End If
    End Sub

    Private Sub c_negChange(ByVal bc As System.Drawing.Color, ByVal deltaValue As Byte, ByVal msg As String) Handles c.negChange
        Label1.BackColor = bc
        Label1.Text = msg
        If ProgressBar1.Value - deltaValue < 0 Then
            ProgressBar1.Value = 0
        Else
            ProgressBar1.Value = ProgressBar1.Value - deltaValue
        End If
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        c.Dispose()
        c = Nothing
    End Sub

End Class
So I changed a few things.
If I fire a message box after the error code like this

            txtError.Visible = True
            txtError.Text = "ERROR: " & vbCrLf & sDateTo
            MessageBox.Show(sDateTo)

Then before I click the ok button on the messagebox I can see the textbox, and it has text, and as soon as I click to ok button e.g. the thread ends it revert bact to its original state.
if the textbox is visible before the thread is run, then the textbox remains visible and the text in the textbox stays after the thread has exited.
How about if the textbox is initially not visible but you use this code?

        txtError.Visible = True
        txtError.Text = "ERROR: " & vbCrLf & sDateTo
        txterror.Refresh()
        Application.DoEvents()
        'MessageBox.Show(sDateTo)

By the way, the test app I wrote also behaves differently if I make the label initially not visible and then toggle its visibility from the event handler.

Idle_Mind
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
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
!!Cheering!!, Im not insane.

That sounds exactly like what I have been experiencing.

I changed its initial state to visible, and then in the load even made it invisible and everything now works as expected.

Sounds like one more thing to thank MS for.

Thanks for all your help
Definitely sounds like a bug.

I think that when the initial state of a control is false in the IDE, it does not actually get loaded with the form.  

When we toggle its visibility for the first time, then the control gets created and added to the forms control collection.

Then, something *waves hands in air magically* about the thread interactions prevents the newly created control from being modified before the thread ends.

I agree with your sentiment!

Thanks Micro$oft...

Idle_Mind