Link to home
Start Free TrialLog in
Avatar of taz8020
taz8020Flag for United Kingdom of Great Britain and Northern Ireland

asked on

Update label on progress bar from different thread.

I need to update a ToolStripStatusLabel  from a different thread, Problem is ToolStripStatusLabel does not have a InvokeRequired, however the StatusStrip1 does but don get how I can use that.

Also If I use the following it is as through the InvokeRequired is false.

  If MainForm.StatusStrip1.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            MainForm.StatusStrip1.Invoke(d, New Object() {UpdateText})
        Else
            MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
End If


So then I tried MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
which does not throw error but does not update text.

Please help.
Avatar of strickdd
strickdd
Flag of United States of America image

You may have to add an "Application.DoEvents" line to force the GUI to update.
Avatar of taz8020

ASKER

Hi strickdd, no just tried this

MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
Application.DoEvents()

Does not work either, dont get error but does not update either. Any other ideas?
Avatar of Mike Tomlinson
It looks like you are using "default instance" syntax for the main form.

How was MainForm initially displayed?  Was it the "startup object" of the project?
...or was it displayed using the "New" keyword?

    Dim mf As New MainForm()
    mf.Show()

You are probably setting the text of the label for the wrong instance of MainForm (not the one being displayed)...
Avatar of taz8020

ASKER

Hi IdleMind, there is a login form that opens the main form without creating an instance it just uses MainForm.Show()

If I try and update a textbox on the main page from the background worker this works fine:
  If MainForm.textbox1.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            MainForm.textbox1.Invoke(d, New Object() {"Test"})
        Else
            MainForm.textbox1.Text = UpdateText
End If

Any Ideas
Hmmm...OK.

You don't actually need to Invoke() against the control itself...the Form will always work.

Also, you can use BeginInvoke() to make it asynchronous:
Public Sub SetText(ByVal UpdateText As String)
    If MainForm.InvokeRequired Then
        Dim d As New SetTextCallback(AddressOf SetText)
        MainForm.BeginInvoke(d, New Object() {UpdateText})
    Else
        MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
    End If
End Sub

Open in new window

Avatar of taz8020

ASKER

Hi Idle_Mind, just tried that but does not work. May be there is something I am doing wrong that I am not telling you both.

1). Loginform opens main form using MainForm.Show()
2). On the main form there is a button to update website.
3). On the click event of the button it calls the background worker which is on the main form.
4). The background worker calls the updateweb function which, this is where the above code is

 Delegate Sub SetTextCallback(ByVal [text] As String)
    Private Sub SetText(ByVal [text] As String)
        MainForm.ToolStripStatusUpdateProgress.Text = [text]
    End Sub

Public Sub UpdateWeb()
'Updates tables here !!!!!!!!!!
    If MainForm.InvokeRequired Then
        Dim d As New SetTextCallback(AddressOf SetText)
        MainForm.BeginInvoke(d, New Object() {UpdateText})
    Else
        MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
    End If

End Sub

Would need to see more code then to understand where the breakdown is.  =\
Avatar of taz8020

ASKER

Ok here is the code used

Private Sub BackgroundWorkerUpdateWinHost_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorkerUpdateWinHost.DoWork
        SyncWinHost()
End Sub

Public Sub SyncWinHost()
  ' Insert  

   UpdateProductsToWinHost(LinkedServerName, LinkedServerDataBaseName,
   LocalServerDataBaseName)

   'Delete

End Sub

Public Function UpdateProductsToWinHost(ByVal LinkedServerName As String, ByVal LinkedServerDataBaseName As String, ByVal LocalServerDataBaseName As String)
        Dim SQLQuery As String
        Dim SQLResult As Integer
        Dim TotalCount As Integer
        Dim StartTime As Date
        Dim UpdateText As String
        Dim TimeTakenInSecs As Integer
        Dim QtyToUpdatePerSQLQuery As Integer = 100

        UpdateText = "Product Update Started..."
        MainForm.ToolStripStatusUpdateProgress.Text = UpdateText

        SQLQuery = "UPDATE Top(" & QtyToUpdatePerSQLQuery & ") WebServerProducts " & _
    "SET " & _
"WebServerProducts.ShortDescription = LocalServerProducts.ShortDescription, " & _
"WebServerProducts.FullDescription = LocalServerProducts.FullDescription, " & _
"WebServerProducts.ItemWeight = LocalServerProducts.ItemWeight, " & _
"WebServerProducts.PriceFromOnly = LocalServerProducts.PriceFromOnly, " & _
"WebServerProducts.SearchTerm1 = LocalServerProducts.SearchTerm1, " & _
"WebServerProducts.SearchTerm2 = LocalServerProducts.SearchTerm2, " & _
"WebServerProducts.SearchTerm3 = LocalServerProducts.SearchTerm3, " & _
"WebServerProducts.SearchTerm4 = LocalServerProducts.SearchTerm4, " & _
"WebServerProducts.SearchTerm5 = LocalServerProducts.SearchTerm5 ," & _
"WebServerProducts.SearchTerm6 = LocalServerProducts.SearchTerm6, " & _
"WebServerProducts.WebSection = LocalServerProducts.WebSection, " & _
"WebServerProducts.LastUpDate = LocalServerProducts.LastUpDate " & _
"FROM [" & LocalServerDataBaseName & "].[dbo].[Products] as LocalServerProducts, [" & LinkedServerName & "]." & LinkedServerDataBaseName & ".dbo.Products as WebServerProducts " & _
"WHERE (WebServerProducts.ProductRef = LocalServerProducts.ProductRef) And (WebServerProducts.LastUpDate Is Null)"
        '"WHERE (WebServerProducts.ProductRef = LocalServerProducts.ProductRef) And (WebServerProducts.LastUpDate < LocalServerProducts.LastUpDate)"
Redo:
        StartTime = Date.Now
 
        SQLResult = RunSQLquery(SQLQuery, "")
        TotalCount += SQLResult
        ' ProductListOpen.LabelRowsSelected.Text = String.Format("{0} Products Updated So Far", TotalCount)
        TimeTakenInSecs = DateDiff(DateInterval.Second, StartTime, Date.Now)

        UpdateText = String.Format("{0} Products Updated On Website, Last {1} updates took {2} Seconds", TotalCount.ToString, QtyToUpdatePerSQLQuery, TimeTakenInSecs)
     
     If MainForm.StatusStrip1.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            MainForm.StatusStrip1.Invoke(d, New Object() {UpdateText})
        Else
            MainForm.ToolStripStatusUpdateProgress.Text = UpdateText
        End If

        If SQLResult > 0 Then
            GoTo Redo
        End If
        Return TotalCount.ToString
    End Function

    Delegate Sub SetTextCallback(ByVal [text] As String)
    Private Sub SetText(ByVal [text] As String)
        MainForm.ToolStripStatusUpdateProgress.Text = [text]
    End Sub
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
Avatar of taz8020

ASKER

I never knew there was a e.UserState, that sounds like it. Will try it now.
You can pass anything (simple types, structs, classes/references, etc) in the second paramter of ReportProgress().

If you need to pass multiple values out then you can use a custom class that holds all of them and then cast e.UserState back to your custom class in the ProgressChanged() event.  =)
Avatar of taz8020

ASKER

No, it still does not work, what you said makes perfect sence but does not work.

I put a break point in the BackgroundWorkerUpdateWinHost_ProgressChanged and can see the UserState has the correct value. But not updating on the main form.
I even added me.text = e.UserState but still does not work. So then added

Private Sub BackgroundWorkerUpdateWinHost_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorkerUpdateWinHost.ProgressChanged
        Me.ToolStripStatusUpdateProgress.Text = e.UserState
        Me.Text = e.UserState
        Application.DoEvents()
End Sub

Still no update. I have been on this all day now and pulling my hair out at this one.

If I use this, it updates the label, but its out side the fuction:

 Private Sub SyncWinHostToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SyncWinHostToolStripMenuItem.Click
        'todo if over 1 hour sync but check last local update first
        If BackgroundWorkerUpdateWinHost.IsBusy = False Then
            BackgroundWorkerUpdateWinHost.RunWorkerAsync()
Me.ToolStripStatusUpdateProgress.Text = "TEST"
        Else
            MsgBox("Process is already updating")
        End If
    End Sub
Ok...something is still tying up the main UI thread despite the BackgroundWorker().

At line #51 you have:

        SQLResult = RunSQLquery(SQLQuery, "")

What is in RunSQLquery()?

Also, do you have any polling loops running somewhere else in the form code?...or anywhere else?
Avatar of taz8020

ASKER

The RunSQLquery is below
There is a lot of code, please explain what you mean by polling loops. There is a timer checking phone calls every 15 secs. within the timer is calles another background worker every 5 mins. I turned these off but still get the same error.

If I put  MsgBox(e.UserState) it does pop up with the message.

Public Function RunSQLquery(ByVal SQLQuery As String, ByVal ConnectionString As String)
        If ConnectionString = "" Then
            ConnectionString = ConnectToCompanyData.DataBaseConnectionString
        End If
        Dim myConnection As New SqlConnection(ConnectionString)

        Dim SQLResult As String
        Dim myCommand As SqlCommand
        myCommand = New SqlCommand(SQLQuery, myConnection)
        Using myConnection
            Try
                myConnection.Open()
                myCommand.CommandTimeout = 5000
                SQLResult = myCommand.ExecuteNonQuery
            Catch ex As Exception
                SQLResult = ex.Message
            End Try
        End Using
        myConnection.Close()
        Return SQLResult
    End Function
That query code looks ok. *as long as it is run from the backgroundworker*

Polling loops are tight looping structures that stay within the loop until some condition is met:

   While Not SomeFlag

   End While

A tight loop somewhere else could prevent the main UI thread from processing the update events.

How is the Timer for the Phone Calls implemented?  Is that using a loop or a Timer control?
Avatar of taz8020

ASKER

One I just tried was updating a label which is in a user control on the main form. It needed the Label1.InvokeRequired but worked on the usercontrol so must be some thing wrong with main form.
You have been more than helpfull on this and cannot thank you enough.

    Private Sub BackgroundWorkerUpdateWinHost_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorkerUpdateWinHost.ProgressChanged
        Me.ToolStripStatusUpdateProgress.Text = e.UserState
        If ProductListOpen.LabelRowsSelected.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            ProductListOpen.LabelRowsSelected.Invoke(d, New Object() {e.UserState})
        Else
            ProductListOpen.LabelRowsSelected.Text = e.UserState
        End If
    End Sub
All controls run in the same main UI thread so if you Invoke() off the Form or use the ProgressChanged() event, which is already Invoked/marshalled for you, then it should work.

How/when were those UserControls created?

Avatar of taz8020

ASKER

I know most people would give up by now but things like this, bug the hell out of me. When the mainform loads is loads as usercontrol called ProductListOpen this is held in a varible until it is required. A menu button then displays it on the form within a panel.

I have to use the Invoked within the ProgressChanged sub. But i thought I could have just used  ProductListOpen.LabelRowsSelected.Text = e.UserState as I thought ProgressChanged would have been on the same thread as the mainform. Does this sound right to you?

Tomorrow I will try the same thing on a new form. If that works I will comment out parts of the main form and see if I can get it to work. Once again you have been more than helpful so will accept this and open a new post. If you could help on the new post I will post the link on this one.
That's correct.  The ProgressChanged() event is already running on the main UI thread so no Invoke() should be necessary.  Post a link here or create a "related question" when you setup the new question.
Avatar of taz8020

ASKER

Hi Idle_Mind, at last got it going, but dont understand why it would cause the problem. The SyncWinHost funcution was in another code.vb file I moved this to the mainform and all works well. I like to put long bits of code in serperate files to make it cleaner. Why would this have caused a problem as they were public?

I also have a problem with a ASP menu that is taking a long time to load. I load the data into an application varible to save loading the data each time. However as it is a big menu it till loops through each row so wanted to try and put the populated control its self in a varible. I have asked this question on here but did not get vey far.
If you know the answer will open a new question and post a link here.
I'm not sure why being in a different file would cause a problem either.  Was it Public in a Module?
I don't work at all with web-forms so I wouldn't be of any help on your other question.  =\
Avatar of taz8020

ASKER

Hi yes it was a Public in a Module, never mind its going now. Once again thank you for your help.