Solved

UI controls don't update via Delegate

Posted on 2014-02-13
39
370 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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
 

Author Comment

by:sidwelle
Comment Utility
its just a simple thread call:

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

Author Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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
Comment Utility
Here is a simple app that does not see to work.
0
 
LVL 62

Expert Comment

by:Fernando Soto
Comment Utility
Where is it?
0
 

Author Comment

by:sidwelle
Comment Utility
Try Again
0
 

Author Comment

by:sidwelle
Comment Utility
3rd Try
0
 

Author Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
Are you trying to post to EE?
0
 
LVL 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
If you can suggest a specific site, I will load it there.
Don't have a skyDive account ?
0
 

Author Comment

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

Accepted Solution

by:
Fernando Soto earned 500 total points
Comment Utility
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
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
Did that answer this question?
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 

Author Closing Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
Thank you.

I will work with it tomorrow and post back.
0
 

Author Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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 62

Expert Comment

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

Author Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
Did the value in txt show up in the label?
0
 

Author Comment

by:sidwelle
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
Which Form is your startup form?
0
 

Author Comment

by:sidwelle
Comment Utility
The Center example: Form2
0
 
LVL 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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
Comment Utility
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 62

Expert Comment

by:Fernando Soto
Comment Utility
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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
Although it can be difficult to imagine, someday your child will have a career of his or her own. He or she will likely start a family, buy a home and start having their own children. So, while being a kid is still extremely important, it’s also …
An introduction to basic programming syntax in Java by creating a simple program. Viewers can follow the tutorial as they create their first class in Java. Definitions and explanations about each element are given to help prepare viewers for future …
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …

743 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

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now