Home All Groups Group Topic Archive Search About

Manipulating controls created by another thread

Author
13 Dec 2006 1:16 PM
Jens
I've read many articles on how to acomplish the above. And I understood it.
The problem just is: In those articles they're always working with only one
class. But what if I have more than one?

Here's my program:

Public Class Form1

    Delegate Sub DisplayStatus(ByVal msg As String)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
        Dim MyWorker As New Worker
        Dim t As New Threading.Thread(AddressOf MyWorker.counter)
        t.Start()
    End Sub

    Public Sub Status(ByVal msg As String)
        TextBox1.Text = msg
    End Sub

End Class

Public Class Worker

    Public Sub counter()
        Dim i As Integer
        For i = 0 To 10000
            Form1.TextBox1.BeginInvoke(New Form1.DisplayStatus(AddressOf
Form1.Status), New Object() {i.ToString})

        Next
    End Sub

End Class

When I try to run it I get the error "Invoke oder BeginInvoke kann für ein
Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde."
Roughly translated to "Invoke or begininvoke can only be run when the
windows handle has been created".

Can anyone tell me what I have to do? I managed to understand the other
stuff with invoke, but I have no idea how to get that handle...

Author
13 Dec 2006 1:34 PM
Robinson
Pass in the for instance when you create the worker, i.e. :

Dim MyWorker As New Worker ( Me )

then invoke on the instance.

Also make sure Button1 is not returning a "dialogresult".  If it is, it
might destroy the form when it is clicked which will result in the handle
being unavailable.
Author
13 Dec 2006 2:00 PM
Jens
I'm sorry, but don't quite get it. Could you paste your lines into my source?

I guess you mean something like this

Public Class Form1

Delegate Sub DisplayStatus(ByVal msg As String)

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim MyWorker As New Worker
Dim t As New Threading.Thread(AddressOf MyWorker.counter)
t.Start()
End Sub

Public Sub Status(ByVal msg As String)
TextBox1.Text = msg
End Sub

End Class

Public Class Worker

Public Sub counter()
Dim i As Integer
For i = 0 To 10000
Form1.TextBox1.BeginInvoke(New Form1.DisplayStatus(AddressOf
Form1.Status), New Object() {i.ToString})

Next
End Sub

End Class

But how can the constructor of the Worker-Class take that parameter? And how
can it be used?
Author
13 Dec 2006 2:20 PM
Robinson
Here, also as branco says ;)  Please note that if you close the form before
the thread has completed, you will get the handle error.  I have introduced
a delay in the thread so you can see it in operation - and am using "Invoke"
rather than "BeginInvoke".

Public Class Form1

Delegate Sub DisplayStatus(ByVal msg As String)

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
    Dim MyWorker As New Worker(Me)
    Dim t As New Threading.Thread(AddressOf MyWorker.counter)
    t.Start()
End Sub

Public Sub Status(ByVal msg As String)
    TextBox1.Text = msg
End Sub

End Class

Public Class Worker

    Private m_Form As Form1

    Public Sub New(ByVal theForm As Form1)
        m_Form = theForm
    End Sub

    Public Sub counter()

        Dim i As Integer

        Dim theDelegate As New Form1.DisplayStatus(AddressOf m_Form.Status)

        For i = 0 To 10000
            m_Form.Invoke(theDelegate, New Object() {i.ToString})
            Threading.Thread.Sleep(100)
        Next

    End Sub

End Class

Show quoteHide quote
"Jens" <J***@discussions.microsoft.com> wrote in message
news:2768AEED-A9A1-4E17-A46A-1B50E215E92F@microsoft.com...
> This seems to be the same problem, but they haven't found a solution, yet:
> http://groups.google.de/group/microsoft.public.dotnet.framework.windowsforms.controls/browse_thread/thread/a8da95805f8bc6ef/38bd026d36a33fa8%2338bd026d36a33fa8
Author
13 Dec 2006 2:07 PM
Branco Medeiros
Jens wrote:
<snip>
Show quoteHide quote
> Public Class Worker
>
>     Public Sub counter()
>         Dim i As Integer
>         For i = 0 To 10000
>             Form1.TextBox1.BeginInvoke(New Form1.DisplayStatus(AddressOf
> Form1.Status), New Object() {i.ToString})
>
>         Next
>     End Sub
>
> End Class
>
> When I try to run it I get the error "Invoke oder BeginInvoke kann für ein
> Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde."
> Roughly translated to "Invoke or begininvoke can only be run when the
> windows handle has been created".
<snip>

The problem with the code above is that you're apparently trying to use
a default instance of Form1 -- a *bad* practice. Pass a reference to
the actual instance of the form and use that.

Something in the likes of:

<aircode>
>     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As _
>     System.EventArgs) Handles Button1.Click
>         Dim MyWorker As New Worker

           MyWorker.Form1 = Me

>         Dim t As New Threading.Thread(AddressOf MyWorker.counter)
>         t.Start()
>     End Sub

> Public Class Worker
     Public Form As Form1
</aircode>

It also seems to me that you don't need to create 10000 (actually
10001) new instances of the delegate. Just one would suffice:

>     Public Sub counter()

           Dim Ds As New Form1.DisplayStatus(AddressOf  Form1.Status)

>         Dim i As Integer
>         For i = 0 To 10000

              Form1.TextBox1.BeginInvoke(Ds, New Object() {i.ToString})

>         Next

HTH.

Regards,

Branco.
Author
13 Dec 2006 2:31 PM
Jens
So, I've tried to integrate your proposals, but still the same error msg:


Public Class Form1

    Delegate Sub DisplayStatus(ByVal msg As String)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
        Dim MyWorker As New Worker
        MyWorker.Form = Me
        Dim t As New Threading.Thread(AddressOf MyWorker.counter)
        t.Start()
    End Sub

    Public Sub Status(ByVal msg As String)
        TextBox1.Text = msg
    End Sub

End Class

Public Class Worker
    Public Form As Form1

    Public Sub counter()
        Dim i As Integer
        Dim DS As New Form1.DisplayStatus(AddressOf Form1.Status)

        For i = 0 To 10000
            Form1.TextBox1.BeginInvoke(DS, New Object() {i.ToString})
        Next

    End Sub

End Class
Author
13 Dec 2006 2:58 PM
Branco Medeiros
Jens wrote:
> So, I've tried to integrate your proposals, but still the same error msg:
<snip>
> Public Class Worker
>     Public Form As Form1
>
>     Public Sub counter()
>         Dim i As Integer
>         Dim DS As New Form1.DisplayStatus(AddressOf Form1.Status)
>
>         For i = 0 To 10000
>             Form1.TextBox1.BeginInvoke(DS, New Object() {i.ToString})
<snip>

This last line should reference the Form variable, not Form1 (I noticed
just now that there's an error in the "aircode" of my previous message,
sorry about that).

Also, you may want to follow Robinson's suggestion, and use Invoke()
instead of BeginInvoke(), for the first will work synchronously -- that
is, it will block until the form processes the message. This may or may
not be what you want... That said, the line above would then read:

           Form.Invoke(DS, New Object() {i.ToString})

Notice that Form is the variable you declared in your Worker class.

HTH.

Regards,

Branco.
Author
13 Dec 2006 3:12 PM
Branco Medeiros
Oops!

I just noticed the following:

>     Public Sub counter()
>         Dim i As Integer
>         Dim DS As New Form1.DisplayStatus(AddressOf Form1.Status)

The above line should obviously read:

         Dim DS As New Form1.DisplayStatus(AddressOf Form.Status)

At this point I should rename this thread to "The hurdles caused by a
misplaced '1' in aircode and similar helping devices", but the actual
issue is "Always follow your best practices even when aircoding."
(which I didn't) =)

Seriously, take a look at Robinson's example, it does the right thing.

Regards,

Branco.
Author
13 Dec 2006 3:27 PM
Jens
NOW IT WORKS :-)

Here's the "final" code (actually it's just the beginning)


Public Class Form1

    Delegate Sub DisplayStatus(ByVal msg As String)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
        Dim MyWorker As New Worker(Me)
        Dim t As New Threading.Thread(AddressOf MyWorker.counter)
        t.Start()
    End Sub

    Public Sub Status(ByVal msg As String)
        TextBox1.Text = msg
    End Sub

End Class

Public Class Worker
    Public Form As Form1

    Public Sub New(ByVal myForm As Form1)
        Form = myForm
    End Sub

    Public Sub counter()
        Dim i As Integer
        Dim DS As New Form1.DisplayStatus(AddressOf Form.Status)

        For i = 0 To 10000
            Form.TextBox1.BeginInvoke(DS, New Object() {i.ToString})
        Next

    End Sub

End Class

Thanks a lot to both of you. I doubt I would have found a solution on my
own...

The improvements will help. The program is supposed to read a file and make
some statistics with it. Since this process may take a while I want the user
to have a status bar, which I couldn't change until now.

Again, thanks a lot!