Results 1 to 5 of 5

Thread: [RESOLVED] [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

  1. #1

    Thread Starter
    Member
    Join Date
    Oct 2013
    Location
    Norway; Haugesund
    Posts
    39

    Resolved [RESOLVED] [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

    [Visual Studio 2010]

    Hello again!

    I'm doing a TCP server project which works great. However, the chat output won't display in my textbox in the main form. I've analyzed the code a little, but I don't seem to find a workaround. Here's the following code I'm using.

    [NOTE] The TCP server runs on a different thread than the UI main thread.

    Code:
    Public Class cmd
        Public Class doCode
            Public Shared Sub Chat(ByVal [Message] As String)
                Functions.OutputString = [Message]
                Functions.EditTextDocument()
            End Sub
        End Class
    
        Public Class Functions
            Public Shared OutputString As String
            Private Delegate Sub DoStuffDelegate()
            Public Shared Sub EditTextDocument()
    
                If (Form1.InvokeRequired) Then 'Checks if the form needs to be invoked.
                    Form1.Invoke(New DoStuffDelegate(AddressOf EditTextDocument)) 'Invokes the form
                Else
                    Form1.txt_Output.Text &= Environment.NewLine & OutputString 'Adds the output stream from the Chat
                End If
            End Sub
    
        End Class
    
    End Class
    When a message from my TCP server is received, it will call the cmd.doCode.Chat(MessageReceived) sub. The function cmd.doCode.Chat will then try to invoke the textbox and add the message for server monitor display.

    What I've found out so far:

    *When I'm using this code in the main class, with all private, the textbox will get updated.
    *When I use it from a different class than the main class, it won't.

    The conclusion I'm drawing from this, is the way I call it Form1.Invoke might create a new thread, instead of using the main UI thread. I then tested with creating some variables to take the Form1 (Me) object on the journey from the beginning, to the end. Still no luck. In another project I fixed this issue by calling a variable at startup,

    Code:
    Dim myForm as Form = Form1
    but it didn't work in this case. I'm not an expert at threading, and might overlooked some important notes with multi-threading. From what I believe so far, if I use multi-threading in another class, objects on invoke won't get updated.

    My question is therefore, how can I update my objects on main UI thread, while doing multi-threading in a different class?

    Thanks for hearing me out, and I hope you can lead me on the right track!

    - Frek
    Last edited by Frekvens1; Feb 2nd, 2015 at 05:48 AM. Reason: More Info

  2. #2
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,401

    Re: [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

    The controls should be private. Nothing has any business knowing they exist other then the form they were created on. Invoke does not create any new thread. Your issue is you are using default instances. You should read JMC's blog on default instances.

    For this example i WILL make the controls public. We can go more into detail later.

    vb Code:
    1. Imports System.Threading
    2.  
    3. Public Class Form1
    4.  
    5.     Private m_form2 As Form2
    6.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    7.         If Me.m_form2 Is Nothing Then
    8.             Me.m_form2 = New Form2
    9.         End If
    10.  
    11.         Me.m_form2.Show()
    12.  
    13.         Dim t As New Thread(Sub() Foo())
    14.         t.IsBackground = True
    15.         t.Start()
    16.     End Sub
    17.  
    18.     Private Sub Foo()
    19.         For n = 0 To 5
    20.             Me.m_form2.RichTextBox1.Invoke(Sub()
    21.                                                Me.m_form2.RichTextBox1.AppendText("Hello UI thread!!!" & vbNewLine)
    22.                                            End Sub)
    23.             Threading.Thread.Sleep(1000)
    24.         Next
    25.     End Sub
    26.  
    27. End Class
    My Github - 1d3nt

  3. #3
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,401

    Re: [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

    Private controls...

    vb Code:
    1. Imports System.Threading.Tasks
    2.  
    3. Public Class Form1
    4.     ''' <summary>
    5.     ''' Holds an instance of form2 initialized on new.
    6.     ''' </summary>
    7.     Private m_form2 As Form2
    8.  
    9.     ''' <summary>
    10.     ''' Creates a new instance of the <see cref="form1" /> Class.
    11.     ''' </summary>
    12.     Public Sub New()
    13.         InitializeComponent()
    14.         Me.m_form2 = New Form2(Me)
    15.         Me.m_form2.Show()
    16.     End Sub
    17.  
    18.     ''' <summary>
    19.     ''' Represents the method that will handle the <see cref="form1.ClientOutput">PostData</see> event of <see cref="ClientOutput" />.
    20.     ''' </summary>
    21.     ''' <remarks >
    22.     ''' The most recent Tcp client data retrieved from the server.
    23.     ''' </remarks>
    24.     Public Event ClientOutput As EventHandler(Of ClientOutputEventArghs)
    25.  
    26.     Protected Overridable Sub OnClientOutput(ByVal e As ClientOutputEventArghs)
    27.         RaiseEvent ClientOutput(Me, e)
    28.     End Sub
    29.  
    30.     Private Sub Button1_Click() Handles Button1.Click
    31.         Dim t As New task(Sub() ExampleTcpOutput())
    32.         t.Start()
    33.  
    34.         Me.Hide()
    35.     End Sub
    36.  
    37.     Private Sub ExampleTcpOutput()
    38.         For n = 0 To 10
    39.             Me.OnClientOutput(New ClientOutputEventArghs("Hello world!!!!"))
    40.             System.Threading.Thread.Sleep(500)
    41.         Next
    42.  
    43.         Me.OnClientOutput(New ClientOutputEventArghs("Completed!!!!"))
    44.     End Sub
    45.  
    46. End Class

    form 2 would look a little like...

    vb Code:
    1. Public Class Form2
    2.  
    3.     Private WithEvents m_form1 As Form1
    4.     Public Sub New(Form1Instance As Form1)
    5.         InitializeComponent()
    6.         Me.m_form1 = Form1Instance
    7.     End Sub
    8.  
    9.     Private Sub ClientOutputEventArghs(sender As Object, e As EventArgs) Handles m_form1.ClientOutput
    10.         Dim value As String = DirectCast(e, ClientOutputEventArghs).clientOutPuteValue
    11.  
    12.         Me.RichTextBox1.Invoke(Sub()
    13.                                    Me.RichTextBox1.AppendText(value & vbNewLine)
    14.                                End Sub)
    15.     End Sub
    16.  
    17. End Class

    and the output class

    vb Code:
    1. Public Class ClientOutputEventArghs
    2.     Inherits EventArgs
    3.     Private ReadOnly m_clientOutPut As String
    4.  
    5.     Public ReadOnly Property clientOutPuteValue As String
    6.         Get
    7.             Return Me.m_clientOutPut
    8.         End Get
    9.     End Property
    10.  
    11.     Public Sub New(ByVal clientOutPut As String)
    12.         Me.m_clientOutPut = clientOutPut
    13.     End Sub
    14. End Class
    My Github - 1d3nt

  4. #4

    Thread Starter
    Member
    Join Date
    Oct 2013
    Location
    Norway; Haugesund
    Posts
    39

    Re: [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

    Quote Originally Posted by ident View Post
    Private controls...

    vb Code:
    1. Imports System.Threading.Tasks
    2.  
    3. Public Class Form1
    4.     ''' <summary>
    5.     ''' Holds an instance of form2 initialized on new.
    6.     ''' </summary>
    7.     Private m_form2 As Form2
    8.  
    9.     ''' <summary>
    10.     ''' Creates a new instance of the <see cref="form1" /> Class.
    11.     ''' </summary>
    12.     Public Sub New()
    13.         InitializeComponent()
    14.         Me.m_form2 = New Form2(Me)
    15.         Me.m_form2.Show()
    16.     End Sub
    17.  
    18.     ''' <summary>
    19.     ''' Represents the method that will handle the <see cref="form1.ClientOutput">PostData</see> event of <see cref="ClientOutput" />.
    20.     ''' </summary>
    21.     ''' <remarks >
    22.     ''' The most recent Tcp client data retrieved from the server.
    23.     ''' </remarks>
    24.     Public Event ClientOutput As EventHandler(Of ClientOutputEventArghs)
    25.  
    26.     Protected Overridable Sub OnClientOutput(ByVal e As ClientOutputEventArghs)
    27.         RaiseEvent ClientOutput(Me, e)
    28.     End Sub
    29.  
    30.     Private Sub Button1_Click() Handles Button1.Click
    31.         Dim t As New task(Sub() ExampleTcpOutput())
    32.         t.Start()
    33.  
    34.         Me.Hide()
    35.     End Sub
    36.  
    37.     Private Sub ExampleTcpOutput()
    38.         For n = 0 To 10
    39.             Me.OnClientOutput(New ClientOutputEventArghs("Hello world!!!!"))
    40.             System.Threading.Thread.Sleep(500)
    41.         Next
    42.  
    43.         Me.OnClientOutput(New ClientOutputEventArghs("Completed!!!!"))
    44.     End Sub
    45.  
    46. End Class

    form 2 would look a little like...

    vb Code:
    1. Public Class Form2
    2.  
    3.     Private WithEvents m_form1 As Form1
    4.     Public Sub New(Form1Instance As Form1)
    5.         InitializeComponent()
    6.         Me.m_form1 = Form1Instance
    7.     End Sub
    8.  
    9.     Private Sub ClientOutputEventArghs(sender As Object, e As EventArgs) Handles m_form1.ClientOutput
    10.         Dim value As String = DirectCast(e, ClientOutputEventArghs).clientOutPuteValue
    11.  
    12.         Me.RichTextBox1.Invoke(Sub()
    13.                                    Me.RichTextBox1.AppendText(value & vbNewLine)
    14.                                End Sub)
    15.     End Sub
    16.  
    17. End Class

    and the output class

    vb Code:
    1. Public Class ClientOutputEventArghs
    2.     Inherits EventArgs
    3.     Private ReadOnly m_clientOutPut As String
    4.  
    5.     Public ReadOnly Property clientOutPuteValue As String
    6.         Get
    7.             Return Me.m_clientOutPut
    8.         End Get
    9.     End Property
    10.  
    11.     Public Sub New(ByVal clientOutPut As String)
    12.         Me.m_clientOutPut = clientOutPut
    13.     End Sub
    14. End Class
    Interesting facts you have there. I've read JMC's blog as recommended, but it didn't help anything too much. However, I found Niya's multi threading article more of use. I've read it before, but I didn't understand much of it at that time. When I compared and analyzed your code with hers, I somehow made an understanding of how the code worked. My TCP server works fine now, including my richtextbox! I recreated my project to fit the new code, and optimized it. I can mark this thread as resolved, as my main question has been solved, but I got another one.

    The way my code works is following:

    [LONG VERSION]

    Main UI thread has a button, when pressed it will go to my TCP class (Class file), where it will start a new thread to do the 'TCP.Server' handling. My TCP server functions in a command based system, where the client sends commands of either login, chat, chat to user, share screen, send file... you name it. When such a command is received with its unique message ID, the TCP server will call my CommandHandler class (Class file). This class analyzes the commands and does the selected functions. Basically, this is where the magic happens.

    The problem I now face, is when a command from my third class has been executed, it needs to be returned to the main UI thread. I think I might set the code up wrong, but in order to make it work; the 'CommandHandler' class needed to be Private only, not shared or something. Which means when I request for the richtextbox output, it will be returned to my TCP class, which runs on a different thread than the main UI thread. For the time being I've added a Public shared variable in the main UI, which the TCP class will update when needed. I currently use a timer to append the output to the richtextbox, as I can't directly touch anything in my UI thread with my TCP class thread. What I'm looking for is something similar, but easier method than Niya's. Like, when the variable's text has been changed, append the text to the richtextbox. I think of that as a way better way than using a timer, which constantly runs and drain memory.

    [SHORT VERSION]

    I have three classes which does my TCP handling, the main UI thread, the TCP class thread and the CommandHandler class. They work like this:

    main UI thread(If server not running) -> Starts the TCP class thread(If message from user received) -> CommandHandler(Execute commands based on messages) class

    The CommandHandler class will output ex. chat string from users, which the Main UI thread wants. But it stops half way in the process, the TCP thread. Is there a way to create a sub/function which will run when a variable's text is changed?

    CommandHandler(Output information) -> TCP thread(Edits Main UI's thread myVariable) -> Main UI thread(Appends text to richtextbox when variable's text has been changed)

    Note, I don't want any timers or unnecessary loops.

    Code:
    Dim myVariable as string
    
    'When myVariable has been changed: Richtextbox1.text &= Environment.NewLine & myVariable
    The Main UI thread can then append new data from the TCP thread, which received its data from the CommandHandler class.



    Anyway, you have my deepest thanks @ident for helping with this frustrating problem!

    With kind regards,

    - Frek
    Last edited by Frekvens1; Jan 31st, 2015 at 02:27 PM. Reason: Spellings

  5. #5

    Thread Starter
    Member
    Join Date
    Oct 2013
    Location
    Norway; Haugesund
    Posts
    39

    Re: [VS 2010] [Multi-Threading] Invoke textbox to apply message, from another class.

    Quote Originally Posted by Frekvens1 View Post
    Interesting facts you have there. I've read JMC's blog as recommended, but it didn't help anything too much. However, I found Niya's multi threading article more of use. I've read it before, but I didn't understand much of it at that time. When I compared and analyzed your code with hers, I somehow made an understanding of how the code worked. My TCP server works fine now, including my richtextbox! I recreated my project to fit the new code, and optimized it. I can mark this thread as resolved, as my main question has been solved, but I got another one.

    The way my code works is following:

    [LONG VERSION]

    Main UI thread has a button, when pressed it will go to my TCP class (Class file), where it will start a new thread to do the 'TCP.Server' handling. My TCP server functions in a command based system, where the client sends commands of either login, chat, chat to user, share screen, send file... you name it. When such a command is received with its unique message ID, the TCP server will call my CommandHandler class (Class file). This class analyzes the commands and does the selected functions. Basically, this is where the magic happens.

    The problem I now face, is when a command from my third class has been executed, it needs to be returned to the main UI thread. I think I might set the code up wrong, but in order to make it work; the 'CommandHandler' class needed to be Private only, not shared or something. Which means when I request for the richtextbox output, it will be returned to my TCP class, which runs on a different thread than the main UI thread. For the time being I've added a Public shared variable in the main UI, which the TCP class will update when needed. I currently use a timer to append the output to the richtextbox, as I can't directly touch anything in my UI thread with my TCP class thread. What I'm looking for is something similar, but easier method than Niya's. Like, when the variable's text has been changed, append the text to the richtextbox. I think of that as a way better way than using a timer, which constantly runs and drain memory.

    [SHORT VERSION]

    I have three classes which does my TCP handling, the main UI thread, the TCP class thread and the CommandHandler class. They work like this:

    main UI thread(If server not running) -> Starts the TCP class thread(If message from user received) -> CommandHandler(Execute commands based on messages) class

    The CommandHandler class will output ex. chat string from users, which the Main UI thread wants. But it stops half way in the process, the TCP thread. Is there a way to create a sub/function which will run when a variable's text is changed?

    CommandHandler(Output information) -> TCP thread(Edits Main UI's thread myVariable) -> Main UI thread(Appends text to richtextbox when variable's text has been changed)

    Note, I don't want any timers or unnecessary loops.

    Code:
    Dim myVariable as string
    
    'When myVariable has been changed: Richtextbox1.text &= Environment.NewLine & myVariable
    The Main UI thread can then append new data from the TCP thread, which received its data from the CommandHandler class.



    Anyway, you have my deepest thanks @ident for helping with this frustrating problem!

    With kind regards,

    - Frek
    It seems people don't see this question. Therefore will I create a new thread with my new question, in better detail. If you want to answer to this problem, follow this link: http://www.vbforums.com/showthread.p...49#post4829349

    - Frek

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width