1

hello I have a problem to update a progress bar and a label inside a StatusStrip in the main form.

there are 2 controls in the form inside a StatusStrip:

  • Progressbar (ToolStripProgressBar)
  • ProgressLabel (ToolStripStatusLabel)

Basically I have this situation:

Public Class Main
    Public Sub TEST(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles TEST.DoWork
          Dim tmp as New NAMESPACE1.CLASS2(VALUES)
    End Sub 
End Class

Namespace NAMESPACE1
    Public Class CLASS2
         Public Sub New(VALUES)
             Main.Progressbar.Value = 15
             Main.ProgressLabel.Text = "hello!"
         End Sub
     End Class 
End Namespace

The problem is that text or value of the controls are updated (I see it using breakpoints) in the code but not in the form in which progressbar is always a 0% and label always as nothing.

I think it's an update or refresh problem of the main form. i have tried to do Main.Refresh() and Main.Update() but it does not work anyway.

Thanks in advance.

Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
Zed Machine
  • 67
  • 1
  • 10
  • 1
    `Main` is a form? If so, `Main` is the class name, possibly not the instance variable in which case, `Main.Progressbar` wont refer to the form/control you see on screen . Dont use default form instances and pass the instance var to anything which needs to use it. – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 14:29
  • ...another issue you will have is accessing UI controls from the Background Worker. `DoWork` is a different thread than that which created the UI controls; an object created on that thread wont be able to reference UI controls. – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 15:04

2 Answers2

1

You have 2 issues in play. The first is that Main is a class name, not a runtime reference or object variable. See Idle_Mind's answer for using Me to get the runtime object reference.

The second problem is that since Class2 is created in DoWork, it is created on the background thread, which will prevent it from accessing UI controls (which are created on the UI thread). You will get an illegal cross thread operation exception (even if you dont see it).

I'd suggest that Class2 does nothing useful which can't be done using the ReportProgress method. Getting rid of it also gets rid of the form reference issue since an event is raised on the same thread as the UI controls:

Private WithEvents bgw As BackgroundWorker

...
' in a button click or whatever starts the worker:
bgw = New BackgroundWorker
bgw.WorkerReportsProgress = True
bgw.RunWorkerAsync(5)         ' times to loop

...
Private Sub bgw_DoWork(sender As Object, 
           e As DoWorkEventArgs) Handles bgw.DoWork

    ' NOTE
    ' This code executes on a different thread
    '   so do not reference UI controls!

    ' e.Argument is the value passed - amount of work
    Dim max As Integer = CInt(e.Argument)

    For n As Integer = 1 To max
        Threading.Thread.Sleep(250)     ' emulates work
        ' causes the ProgressChanged event to fire:
        bgw.ReportProgress(n, String.Format("{0} of {1}", n.ToString, max.ToString))
    Next

End Sub

Private Sub bgw_ProgressChanged(sender As Object, 
                e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
    'ProgressChanged fires on the UI thread, so it is safe to 
    ' referenece controls here
    TextBox4.Text = e.UserState.ToString
    TextBox4.Refresh()
End Sub

Paste the code and you can see the message change in the TextBox. The same would work using your ProgressBar and ProgressLabel.

bgw.ReportProgress(n, arg)

The first argument will map to e.ProgressPercentage in the ProgressChanged event. The second is optional - UserState. I used it to pass a string for illustrative purposes (the form can already know the amount of work since it told the BGW what to do.)


If Class2 has some other purpose, you can use it as long as it is created on the UI thread (in the form) and used on that thread (ie in ProgressChanged event). You also need a method to talk to the controls so you dont have to create a new one each time:

Private myObj As Class2           ' declaration
...
myObj = New Class2(Me)             ' instance with frm ref

In class2:

Public Sub Update(value As Integer, msg As String)
   frmMain.Progressbar.Value = value
   frmMain.ProgressLabel.Text = msg
End Sub

Then in the ProgressChanged event:

myObj.Update(x, y)

Where x and y are the value and message from whereever.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • thanks for all replies. passing me Me as main results in a warning as plutonix says. Cross-thread operation not allowed. I will try to do as you said even if I have not completely understand it. – Zed Machine Jul 20 '15 at 16:29
  • you should use `ReportProgress` to talk to UI controls from the BGW thread (as in the answer). It exists to make threading easier. – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 16:32
  • while doing some tests I discover that the exception is generated only when i set Progressbar value while updating the label do not raise exception – Zed Machine Jul 20 '15 at 16:35
  • I'd have to see your code, but accessing a UI control directly from the BGW should always result in the exception - sometime it is not reported. – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 16:41
  • I cannot delete Class2... there is another way to make progressbar work? – Zed Machine Jul 20 '15 at 16:43
  • as the answer explains, just create it on the UI thread and invoke it from the `ProgressChanged` event. It *is* wonky to perform actions in the constructor though - you need a method so that you do not need a new class instance each time you need to update the progress controls – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 16:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83779/discussion-between-zed-machine-and-plutonix). – Zed Machine Jul 20 '15 at 16:53
  • actually, you've changed the question context dramatically using delegates...and "Class2" seems to have disappeared. Why cant you use ReportProgress to update the UI? Sorry, chat is not possible. – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 16:57
  • here is corrected code http://pastebin.com/eTHkUPpC... sorry I'm not familiar with multi-threading and it's difficult for me. If I use a Public Sub declared in Main class and use it to update controls? of course called as Me.frmmain.UpdateProgressBar(23) and Me.frmmain.UpdateProgressLabel("hello") ? – Zed Machine Jul 20 '15 at 17:10
  • My advice in the edit doesnt change: the ctor (Sub New) is the wrong place to perform updates from - it requires that you create a new `Class2` each time *which you cant do from the BGW thread*. The edit shows how to use a method for that - but since it **must** update from the Progress changed event, it is largely superflous. [here is another](http://stackoverflow.com/a/27568395/1070452) example – Ňɏssa Pøngjǣrdenlarp Jul 20 '15 at 17:14
0

Here's an example of passing a reference to MAIN as suggested by Plutonix. I've intentionally left your pseudo-code style intact:

Public Class MAIN

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        TEST.RunWorkerAsync()
    End Sub

    Private Sub TEST_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles TEST.DoWork
        Dim tmp As New NAMESPACE1.CLASS2(Me, VALUES) ' <-- Form reference being passed via 1st parameter
    End Sub

End Class

Namespace NAMESPACE1

    Public Class CLASS2

        Private frmMain As MAIN

        Public Sub New(ByVal frmMain As MAIN, VALUES)
            Me.frmMain = frmMain

            Me.frmMain.Progressbar.Value = 15
            Me.frmMain.ProgressLabel.Text = "hello!"
        End Sub

    End Class

End Namespace
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40