Solved

UI controls don't update via Delegate

Posted on 2014-02-13
39
373 Views
Last Modified: 2014-02-15
Please tell me what was wrong with the following snip, Thanks  

 Private Delegate Sub StatusDelegate(ByVal txt As String)
    Public Function Status(ByVal txt As String) As Integer

        'If Me.InvokeRequired Then
        If Me.lblStatus.InvokeRequired Then
            'Me.lblStatus.Invoke(New StatusDelegate(AddressOf Status))
            'Status`.Invoke(New SetText(AddressOf SetLabelText), Text)
            Me.Invoke(New StatusDelegate(AddressOf Status), Text)

        Else
            Me.lblStatus.Text = txt
            'Me.lblStatus.Text = "test ..."
            'Me.Text = "test ..."
            Me.lblStatus.Update()
            Application.DoEvents()
        End If

    End Function

Open in new window

0
Comment
Question by:sidwelle
  • 21
  • 17
39 Comments
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39856523
Hi sidwelle;

The Delegate type should match  the type you are creating. So try it this way. Also when you execute the Status function and the Invoke is executed pass the same parameter as txt not Text.

Private Delegate Function StatusDelegate(ByVal txt As String) As Integer

' This should be a function and not a Sub and should return an Integer
Public Function Status(ByVal txt As String) As Integer

    'If Me.InvokeRequired Then
    If Me.lblStatus.InvokeRequired Then
        'Me.lblStatus.Invoke(New StatusDelegate(AddressOf Status))
        'Status`.Invoke(New SetText(AddressOf SetLabelText), Text)
        Me.Invoke(New StatusDelegate(AddressOf Status), txt)
    Else
        Me.lblStatus.Text = txt
        'Me.lblStatus.Text = "test ..."
        'Me.Text = "test ..."
        Me.lblStatus.Update()
        'Not needed
        'Application.DoEvents()
    End If

End Function

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39856560
I made those changes, but I still don't get an update.

I can trace the call in to the 'Status' function and see the value of what was passed and assigne to the text property, but the never does update.

One Time I dragged the exection into the Invoke side of the If statment just to see the Invoke work, but it throws an error: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

Whats with that ?
Is it not call the main thread ?
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39856613
From where are you calling this function from? If you want to see the Invoke work it should be called from a different thread. Please show the code where you are calling it from.
0
Resolve Critical IT Incidents Fast

If your data, services or processes become compromised, your organization can suffer damage in just minutes and how fast you communicate during a major IT incident is everything. Learn how to immediately identify incidents & best practices to resolve them quickly and effectively.

 

Author Comment

by:sidwelle
ID: 39856786
its just a simple thread call:

        Dim listener As New Thread(AddressOf main)
        listener.Start(port)
0
 

Author Comment

by:sidwelle
ID: 39856793
If I set a breakpoint in the Status function, can hover over lblStatus.text and it show the value as assigned.

But the control does not paint !?
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39856809
If the project is not to big and if you are allowed to zip up the complete project into a zip file and post to the web such as SkyDrive or some other location so that I can download it and try it.

If that is not possible please put together a small test project which show the issue you are having. Then post to the web. Do not post to EE because it will not allow some of the files types in the project.
0
 
LVL 13

Expert Comment

by:Naman Goel
ID: 39856835
I tried the below code and it is working as expected, DoEvents is not at all required:
Imports System.Threading

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim t As Thread
        t = New Thread(AddressOf ThreadTask)
        t.IsBackground = True
        t.Start()
    End Sub

    Private Delegate Function StatusDelegate(ByVal txt As String) As Integer

    Public Function Status(ByVal txt As String) As Integer

        'If Me.InvokeRequired Then
        If Me.lblStatus.InvokeRequired Then
            'Me.lblStatus.Invoke(New StatusDelegate(AddressOf Status))
            'Status`.Invoke(New SetText(AddressOf SetLabelText), Text)
            Me.Invoke(New StatusDelegate(AddressOf Status), txt)

        Else
            Me.lblStatus.Text = txt


            'Me.lblStatus.Text = "test ..."
            'Me.Text = "test ..."
        End If
        Return 0
    End Function

    Private Sub ThreadTask()
        Dim stp As Integer
        While True
            stp = stp + 1
            Thread.Sleep(1000)
            Status(stp.ToString())
        End While

        
    End Sub


End Class

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39857049
Here is a simple app that does not see to work.
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39857052
Where is it?
0
 

Author Comment

by:sidwelle
ID: 39857069
Try Again
0
 

Author Comment

by:sidwelle
ID: 39857089
3rd Try
0
 

Author Comment

by:sidwelle
ID: 39857099
Its not going to let me upload, so I just pasted it. Looks alot like yours.

Imports System.Threading

Public Class Form1

    'Private WithEvents MyClass1 As New SomeClass

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        'MyClass1.Starter()
        'Starter()

        Dim t As New Thread(AddressOf Main)
        t.IsBackground = True
        t.Start()


    End Sub

    Private Delegate Function StatusDelegate(ByVal txt As String) As Integer
    Public Function Status(ByVal txt As String) As Integer

        'If Me.InvokeRequired Then
        If Me.Label1.InvokeRequired Then
            'Me.lblStatus.Invoke(New StatusDelegate(AddressOf Status))
            'Status`.Invoke(New SetText(AddressOf SetLabelText), Text)
            Me.Invoke(New StatusDelegate(AddressOf Status), txt)

        Else
            Me.Label1.Text = txt
            Me.Text = txt

            Me.Label1.Update()
            'Application.DoEvents()

            'MsgBox(txt)
        End If

    End Function

    Public Function Starter() As Integer

        'Dim t As New Thread(AddressOf Main)
        't.IsBackground = True
        't.Start()

    End Function

    Public Shared Function Main() As Integer

        Form1.Status("This is a test ...")

    End Function



End Class

Public Class SomeClass

    'Public Function Starter() As Integer

    '    Dim t As New Thread(AddressOf Main)
    '    t.IsBackground = True
    '    t.Start()

    'End Function

    'Public Shared Function Main() As Integer

    '    Form1.Status("This is a test ...")

    'End Function

End Class
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39857103
Are you trying to post to EE?
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39857130
As I stated in a previous post, "Do not post to EE because it will not allow some of the files types in the project.", That is Why I suggested to use A SkyDrive account which comes with windows live account which cost nothing to have. Or you could use some other website.
0
 

Author Comment

by:sidwelle
ID: 39857146
If you can suggest a specific site, I will load it there.
Don't have a skyDive account ?
0
 

Author Comment

by:sidwelle
ID: 39857151
My code is almost identical to what is posted. Is there some setting in the project that I don't have ?
0
 
LVL 63

Accepted Solution

by:
Fernando Soto earned 500 total points
ID: 39857553
The offending code

Public Shared Function Main() As Integer

    Form1.Status("This is a test ...")

End Function

Open in new window

Making this function Shared means there is only one copy of this code to be used by all instances of the class. You do not want all instances to use only one copy because if you have multiple instances calling the code it will trip all over itself and not what you want.

Because you made the function Main Shared you could not reference the Label control of your instance because you can not reference a instance variable from a Shared function and therefore the reason you used Form1 and not Me the instance variable. Although it looked like it was working when you ran it it did not know which instance to update because you used Form1 and not Me.

Make the following changes and it should work.

Public Function Main() As Integer

    Me.Status("This is a test ...")

End Function

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39857697
Removing the keyword "Shared" fixed the small app.

2 Conclusions.
1.) Main (threaded) function can't be "Shared".  You explanation explains the err msg I was receiving (Invoke or BeginInvoke cannot be called on a control until the window handle has been created.)
2.) You must make the call back from the same FORM class ? (Not another class ?)
Thanks
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39857782
Did that answer this question?
0
 

Author Closing Comment

by:sidwelle
ID: 39858105
Thanks for the help.

Was not really  sure what 'Shared' meant, I just copied it from another example.
Can I get your opinion on #2 ?   I would like to be able to build another class or module and call back and update the form, if its possible.

Thanks
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39858172
Hi sidwelle;

In order to create and run the thread in another class and have it access Form1 you will need to pass it a reference to Form1 when you create the other class. Below is a code snippet to show how it can be done. Please note that I turned all your Function's to Sub's because non of them were returning any values.

Imports System.Threading

Public Class Form1


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' Create an instance of the class that will be running the thread. Note that I am passing a reference to this Form in the constructor.
        Dim sClass As New SomeClass(Me)
        ' Start the thread in the SomeClass instance
        sClass.Starter()

    End Sub

    Private Delegate Sub StatusDelegate(ByVal txt As String)

    Public Sub Status(ByVal txt As String)

        If Me.Label1.InvokeRequired Then
            Me.Invoke(New StatusDelegate(AddressOf Status), txt)
        Else
            Me.Label1.Text = txt
            Me.Text = txt
            Me.Label1.Update()
        End If

    End Sub

End Class

Public Class SomeClass

    ' Variable that holds a reference to the Form that will be called to write to the label on.
    Private callingForm As Form1
 
    ' Constructor being passed a reference to the Form that will be modified
    Public Sub New(ByVal f As Form1)
        callingForm = f
    End Sub

    Public Sub Starter()
        Dim t As New Thread(AddressOf Main)
        t.IsBackground = True
        t.Start()
    End Sub

    ' Note that I am using the reference to the other Form when calling Status. 
    Public Sub Main()
        callingForm.Status("This is a test ...")
    End Sub

End Class

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39858187
Thank you.

I will work with it tomorrow and post back.
0
 

Author Comment

by:sidwelle
ID: 39860216
I can get it to work, few points ...
1.)  I don't exactly know why it wants "f as form1" and not "f as form" in my head it should accept either ?
I know that from1 will tie you back to a copy that exact form, but why is it so exclusive and not accept any form name ?

2.)  When the Delegate is called it seems to release the second thread, it acts like it waits for a value to be returned, but when the execution gets back to the line in the second thread that called it, the value returned is "nothing" ?
I need to know how to get a value back to the line in the 2nd class that called the function in the IU class.

Again:
I need to know how to get the "Status" function to return a value to the 2nd thread.

I noticed that I can assign return values if the function is not delegated, I can even read form controls ?
Do I only need to use the invoke mechanism when updating a control ?


Thanks
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860322
To your questions:

1. Because you need to access the form you will be interacting with or an instance of it and Form is not the right form. Form doest not have a Status Sub and a StatusDelegate definition and if it does it's not the right one .

2. Not sure what you are saying here can you provide more details on this question.

To this question, "I need to know how to get the "Status" function to return a value to the 2nd thread.", Change the delegate as follows, where ReturnDataType is the datatype being returned:

Private Delegate Function StatusDelegate(ByVal txt As String) As ReturnDataType

and change the Status Sub to a Function as follows, where ReturnDataType is the same type as the delegate.:

Public Function Status(ByVal txt As String) As ReturnDataType

Inside the Status function place a Return statement with the variable being returned

To your question, "Do I only need to use the invoke mechanism when updating a control ?", According to the documentation when accessing a controls property there are only four functions that are thread safe and there are Invoke, BeginInvoke, EndInvoke, and CreateGraphics anything else can cause issues.
0
 

Author Comment

by:sidwelle
ID: 39860578
Tried using the "Return" statement, but the result is the same.
I had seen that before in other examples, just thought someone typed in some java ?
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860620
You are not using the Return statement to replace the txt variable are you?
0
 

Author Comment

by:sidwelle
ID: 39860832
In the following example, a value of 1 is returned, I never did see a value of 2.
Even when I commented each of them out.

    Private Delegate Function StatusDelegate(ByVal txt As String) As Integer
    Public Function Status(ByVal txt As String) As Integer

        If Me.Label1.InvokeRequired Then
            Me.Invoke(New StatusDelegate(AddressOf Status), txt)
            Return 1
        Else
            Me.Label1.Text = txt
            Return 2
        End If

    End Function

Open in new window

0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860840
Did the value in txt show up in the label?
0
 

Author Comment

by:sidwelle
ID: 39860847
Yes.

I get the value of 1 with either:

return 1
or
Status = 1

When placed in the If statement before the else.
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860851
Can you post the code that you are using so that I can drop it into a project to see what you are trying to do.
0
 

Author Comment

by:sidwelle
ID: 39860852
The following example lets me pass any form name that I want: (it has to 'Me' if passing the form that you calling from)

Public Class Form2
    Private Sub cmdShowFrm3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdShowFrm3.Click
        Form3.ShowMe(Me)
    End Sub
End Class

Public Class Form3
    Private cf As Form

    Public Function ShowMe(ByVal f As Form) As Integer
        cf = f
        Me.Show()
    End Function

    Private Sub Form3_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        'Center me on top of calling form ...
        Dim iMidWidth As Integer = cf.Left + cf.Width / 2
        Dim iMidHeight As Integer = cf.Top + cf.Height / 2

        Me.Left = iMidWidth - (Me.Width / 2)
        Me.Top = iMidHeight - (Me.Height / 2)

    End Sub
End Class

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39860856
Here is the other example:

Imports System.Threading

Public Class Form1
    Private WithEvents MyClass1 As New SomeClass(Me)

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        MyClass1.Starter(Me)
        'Starter()

        'Dim t As New Thread(AddressOf Main)
        't.IsBackground = True
        't.Start()

    End Sub

    Private Delegate Function StatusDelegate(ByVal txt As String) As Integer
    Public Function Status(ByVal txt As String) As Integer

        If Me.Label1.InvokeRequired Then
            Me.Invoke(New StatusDelegate(AddressOf Status), txt)
            'Return 1
            Status = 1
        Else
            Me.Label1.Text = txt
            Me.Text = txt
            Beep()

            'Return 2
            Status = 2
        End If

    End Function

    'Public Function Starter() As Integer

    '    Dim t As New Thread(AddressOf Main)
    '    t.IsBackground = True
    '    t.Start()

    'End Function

    'Public Function Main() As Integer

    '    Thread.Sleep(2000)
    '    Me.Status("This is a test ...")

    'End Function

    Public Class SomeClass
        Private frm As Form1

        Public Function Starter(ByVal f As Form1) As Integer

            Dim t As New Thread(AddressOf Main)
            t.IsBackground = True
            t.Start()

        End Function

        Public Function Main() As Integer
            Dim iRtn As Integer

            Thread.Sleep(500)
            iRtn = frm.Status("This is a test ...")

            MsgBox("Value Returned: " & iRtn.ToString)
        End Function

        Public Sub New(ByVal f As Form1)
            frm = f
        End Sub
    End Class

End Class

Open in new window

0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860870
Which Form is your startup form?
0
 

Author Comment

by:sidwelle
ID: 39860873
The Center example: Form2
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860884
OK using Form2 as the start up form. Click on the button and it opens Form3 on top of Form2. Form1 never gets created and no thread is called no label gets fill.

Do you have other code that creates Form1 and runs the thread?
0
 

Author Comment

by:sidwelle
ID: 39860893
No, I meant them to ack as two seperate projects.

With the Center example, I was trying to demonstrate that IDE does not force me to use the name of a specific form in the defination of the function:

Public Function ShowMe(ByVal f As Form) As Integer

Open in new window

0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39860933
To your statement, "With the Center example, I was trying to demonstrate that IDE does not force me to use the name of a specific form in the defination of the function:"; this is true but that is because all Form's inherits from Windows Form control they all have the same basic properties. So Form1, Form2 and Form3 can all be cast to a Form type. But what you can NOT do is have a function in Form3 let say and have that function called PrintDisplay. If you try the same thing then it will NOT work because a object of type Form does not have a function called PrintDisplay you would need to cast it to a type Form3.

The reason you was seeing only return of 1 you was only displaying the return value of the last call only. Make the below changes and you will see both.

Public Function Status(ByVal txt As String) As Integer

    If Me.Label1.InvokeRequired Then
        ' When the next line is executed this thread waits until the else is done
        Dim iRetMainThread As Integer = Me.Invoke(New StatusDelegate(AddressOf Status), txt)
        ' Comming back from GUI thread will print 2 here
        MsgBox("Return value from main thread" & iRetMainThread)
        ' Now return 1 to the original caller of this function.
        'Return 1
        Status = 1
    Else
        Me.Label1.Text = txt
        Me.Text = txt
        Beep()

        'Return 2
        Status = 2
    End If 
                                                       
End Function

Public Function Main() As Integer
    Dim iRtn As Integer

    Thread.Sleep(500)
    iRtn = frm.Status("This is a test ...")

    MsgBox("Value Returned Non GUI thread: " & iRtn.ToString)
End Function

Open in new window

0
 

Author Comment

by:sidwelle
ID: 39861923
This is the line that I wanted to see, Simple now that you see it.

Status = Me.Invoke(New StatusDelegate(AddressOf Status), txt)

I want to thank you for your help. You have gone above and beyond most of the other experts that answer with a one liner.

If I have any more questions, I will post a new question.

Thanks
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 39861935
Thank you. The way I see it if I am to be of help then do it the right way by making sure what I post is clear and not just a one liner.

Have a great day.
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

In this post we will learn how to connect and configure Android Device (Smartphone etc.) with Android Studio. After that we will run a simple Hello World Program.
Computer science students often experience many of the same frustrations when going through their engineering courses. This article presents seven tips I found useful when completing a bachelors and masters degree in computing which I believe may he…

861 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