Question Client/Server formatted text sending/receiving??

anubhavmax

Member
Joined
Jan 21, 2009
Messages
10
Programming Experience
Beginner
I have been wrting a code to develop an Instant messenger by modifying an open source code project available at
Home-brew Your Own Instant Messenger App with Visual Studio .NET

Instead of the send message text box and message display text box, I have replaced it with RichTextBox for text formatting (bold/italics/underline/font/color). The problem I am facing is when I format the text and click on send, then the text displayed on the message display box (txtMessageHistory) is unformatted.

Here is the code for server--

VB.NET:
Public Class ChatClient
    '---contains a list of all the clients
    Public Shared AllClients As New HashTable

    '---information about the client
    Private _client As TcpClient
    Private _clientIP As String
    Private _ClientNick As String

    '---used for sending/receiving data
    Private data() As Byte

    '---is the nick name being sent?
    Private ReceiveNick As Boolean = True

Public Sub New(ByVal client As TcpClient)
        _client = client

        '---get the client IP address
        _clientIP = client.Client.RemoteEndPoint.ToString

        '---add the current client to the hash table
        AllClients.Add(_clientIP, Me)

        '---start reading data from the client in a 
        '   separate thread
        ReDim data(_client.ReceiveBufferSize)
        _client.GetStream.BeginRead(data, 0, _
           CInt(_client.ReceiveBufferSize), _
           AddressOf ReceiveMessage, Nothing)
    End Sub

  <b>Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        '---read from client---
        Dim bytesRead As Integer
        Try
            SyncLock _client.GetStream
                bytesRead = _client.GetStream.EndRead(ar)
            End SyncLock
            '---client has disconnected
            If bytesRead < 1 Then
                AllClients.Remove(_clientIP)
                Broadcast(_ClientNick & _
                              " has left the  chat.")
                Exit Sub
            Else
                '---get the message sent
                Dim messageReceived As String = _
                    System.Text.Encoding.ASCII. _
                    GetString(data, 0, bytesRead)
                '---client is sending its nickname
                If ReceiveNick Then
                    _ClientNick = messageReceived

                    '---tell everyone client has entered 
                    '   the chat
                    Broadcast(_ClientNick & _
                       " has joined the chat.")
                    ReceiveNick = False
                Else
                    '---broadcast the message to everyone
                    Broadcast(_ClientNick & ">" & _
                       messageReceived)
                End If
            End If
            '---continue reading from client
            SyncLock _client.GetStream
                _client.GetStream.BeginRead(data, 0, _
                   CInt(_client.ReceiveBufferSize), _
                   AddressOf ReceiveMessage, Nothing)
            End SyncLock

        Catch ex As Exception
            AllClients.Remove(_clientIP)
            Broadcast(_ClientNick & _
               " has left the chat.")
        End Try
    End Sub</b>

Public Sub SendMessage(ByVal message As String)
        Try
            '---send the text
            Dim ns As System.Net.Sockets.NetworkStream
            SyncLock _client.GetStream
                ns = _client.GetStream
            End SyncLock
            Dim bytesToSend As Byte() = _
             System.Text.Encoding.ASCII.GetBytes(message)
            ns.Write(bytesToSend, 0, bytesToSend.Length)
            ns.Flush()
        Catch ex As Exception
            Console.WriteLine(ex.ToString)
        End Try
    End Sub
Finally, the Broadcast() function sends a message to all the clients stored in the AllClients HashTable object. 

    Public Sub Broadcast(ByVal message As String)
        '---log it locally
        Console.WriteLine(message)
        Dim c As DictionaryEntry
        For Each c In AllClients
            '---broadcast message to all users
            CType(c.Value, _
               ChatClient).SendMessage(message & vbCrLf)
        Next
    End Sub

The problem here is whenever the server sends or receives a message from the client, it receives it in the form of a string that has no formatting in it.

here is the code for the client-
VB.NET:
Public Sub SendMessage(ByVal message As String)
        Try
            '---Send A Message To The Server
            Dim ns As NetworkStream = client.GetStream
            Dim data As Byte() = _
            System.Text.Encoding.ASCII.GetBytes(message)
            '---Send The Text
            ns.Write(data, 0, data.Length)
            ns.Flush()
        Catch ex As Exception
            MsgBox("Server Disconnected")
            'MsgBox(ex.ToString)
        End Try
    End Sub

    Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        Try
            Dim bytesRead As Integer
            bytesRead = client.GetStream.EndRead(ar)
            If bytesRead < 1 Then
                Exit Sub
            Else
                Dim para() As Object = _
                   {System.Text.Encoding.ASCII.GetString( _
                   data, 0, bytesRead)}
                Me.Invoke(New delUpdateHistory( _
                   AddressOf Me.UpdateHistory), para)
            End If
            client.GetStream.BeginRead( _
               data, 0, CInt(client.ReceiveBufferSize), _
               AddressOf ReceiveMessage, Nothing)
        Catch ex As Exception
        End Try
    End Sub


Private Sub btnSignIn_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnSignIn.Click
        If btnSignIn.Text = "Sign In" Then
            Try
                '---Connect To The Server
                client = New TcpClient
                client.Connect(TextBox1.Text, portNo)
                ReDim data(client.ReceiveBufferSize)
                SendMessage(txtNick.Text)
                MsgBox("Successfully logged in")
                '---Read From The Server
                client.GetStream.BeginRead( _
                   data, 0, CInt(client.ReceiveBufferSize), _
                   AddressOf ReceiveMessage, Nothing)
                btnSignIn.Text = "Sign Out"
                btnSend.Enabled = True
            Catch ex As Exception
                MsgBox("Server is not reachable")

            End Try
        Else
            '---Disconnect From The Server By Signing Out From The Client
            Disconnect()
            MsgBox("Successfully logged off")
            btnSignIn.Text = "Sign In"
            btnSend.Enabled = False
        End If
    End Sub

    Public Sub Disconnect()
        '---Disconnect From The Server By Closing The Client
        Try
            client.GetStream.Close()
            client.Close()
        Catch ex As Exception
            'MessageBox.Show("Session Closed ")
        End Try
    End Sub

    '---Delegate And Subroutine To Update The TextBox Control
    Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.AppendText(Str)
    End Sub
    '--- Disconnect() method to Disconnect The Client From The Server And Close The Client 
    Private Sub Form1_FormClosing( _
       ByVal sender As Object, _
       ByVal e As System.Windows.Forms.FormClosingEventArgs) _
       Handles Me.FormClosing
        Disconnect()
    End Sub

Rest of the code of client is related to formatting of the rich text boxes that is not relevant to my problem.

I just need to transfer the formatted text from clint to server and then back to the client the same way and display the message with text formatting. Any kind of help would relly be appreciated.
 
Last edited by a moderator:
I'm using RichTextBox control and like the formatted text, when I view the text from Text property I see just plain text. I know that the formatting is not magic and it should be available somewhere. What do I do? I look in help for RichTextBox class and browse the properties, if there is a plain Text property there should be a FormattedText property right? No, there is not, so perhaps it has a different name? And there are so many properties, must over 100, I have no interest in all these properties, I just want the formatted text. Starting to think, I figure not all controls has formatted text so what I want in probably not inherited from the TextBoxBase, I mean the TextBox control does not have formatted text, so I filter out inherited properties using the Members filter on top of page to look at only those specific to the RichTextBox control. Now it's starting to look like something, and what do I quickly see? There is a property with a weird name, "Rtf", that is described as "Gets or sets the text of the RichTextBox control, including all rich text format (RTF) codes.". That sounds very promising, doesn't it?
 
I am a complete noob to vb.net . Can you please give me the example of a code with proper syntax or if possible edit the part of the code that I may hav mistaken

I changed the first part of the client code to
<code>
Private Sub btnSend_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnSend.Click
SendMessage(txtMessage.<b>Rtf</b>)
txtMessage.Clear()
End Sub

Do i need to add any further statement or declaration to the code. Beacuse changing this gives the error when I send message.

</code>

anubhavmax says : {\rtf1\ansi\ansicpg1252\deff0\deflang16393{\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}
\viewkind4\uc1\pard\i\f0\fs17 sfsfs\i0\par
}
 
Beacuse changing this gives the error when I send message
What error?

I see your "receiver" uses AppendText, which appends plain text. You don't want plain text, you want rtf text. If you're not setting all RTF of the richtextbox in one go you perhaps just want to have another look in help, is there a property that allow you to get/set only part of text with formatting? Yes, there is one called SelectedRTF.

Note that your implementation of the communication is not safe now. For plain ascii text where one char is one byte there is no problem requesting read of an arbitrary number of bytes and translating these to ascii chars as the bytes arrive. For other encodings this would not work as one char could also be two bytes, and you never know what you get in each read. For rtf formatted text the whole transmission needs to be intact, whether you send the whole content of formatted text or a selection, think of the formatted text you get as a single object that you can't chop in half. Lets say this object is 1000 bytes, and your receiver only get to read it in chunks of 100 bytes. The receiver then needs to know first that there are 1000 bytes in total, it then reads 100 bytes 10 times and know it has got the whole rtf object, only then it can offer the richtextbox this complete rtf string to Rtf property or SelectedRtf property.

However important it is for you to learn the basics and how socket programming works at primitive level, using a BinaryReader can be much easier, because it operates on the stream and can read from it as bytes continue to arrive over the wire. The writer counterpart, the BinaryWriter, also uses this trick that it automatically writes the string length first so the reader will know how much to read. The BinaryWriter/Reader you create and wrap around the NetworkStream like this:
VB.NET:
writer = New IO.BinaryWriter(client.GetStream)
You keep this writer/reference open for as long the client/stream is open, writing a string to stream is done like this:
VB.NET:
writer.Write("message")
 
Yes got your point. But on networking I am an absolute noob and don't want security at this point. Just want to send and display the Rtf successfully.
Here is my revised client code
VB.NET:
[B]Private Sub btnSend_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnSend.Click
        SendMessage(txtMessage.Rtf)
        txtMessage.Clear()
    End Sub[/B]

Public Sub SendMessage(ByVal message As String)
        Try
            '---Send A Message To The Server
            Dim ns As NetworkStream = client.GetStream
            Dim data As Byte() = _
            System.Text.Encoding.ASCII.GetBytes(message)
            '---Send The Text
            ns.Write(data, 0, data.Length)
            ns.Flush()
        Catch ex As Exception
            MsgBox("Server Disconnected")
            'MsgBox(ex.ToString)
        End Try
    End Sub

Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        Try
            Dim bytesRead As Integer
            bytesRead = client.GetStream.EndRead(ar)
            If bytesRead < 1 Then
                Exit Sub
            Else
                Dim para() As Object = _
                   {System.Text.Encoding.ASCII.GetString( _
                   data, 0, bytesRead)}
                Me.Invoke(New delUpdateHistory( _
                   AddressOf Me.UpdateHistory), para)
            End If
            client.GetStream.BeginRead( _
               data, 0, CInt(client.ReceiveBufferSize), _
               AddressOf ReceiveMessage, Nothing)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnSignIn_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnSignIn.Click
        If btnSignIn.Text = "Sign In" Then
            Try
                '---Connect To The Server
                client = New TcpClient
                client.Connect(TextBox1.Text, portNo)
                ReDim data(client.ReceiveBufferSize)
                SendMessage(txtNick.Text)
                MsgBox("Successfully logged in")
                '---Read From The Server
                client.GetStream.BeginRead( _
                   data, 0, CInt(client.ReceiveBufferSize), _
                   AddressOf ReceiveMessage, Nothing)
                btnSignIn.Text = "Sign Out"
                btnSend.Enabled = True
            Catch ex As Exception
                MsgBox("Server is not reachable")

            End Try
        Else
            '---Disconnect From The Server By Signing Out From The Client
            Disconnect()
            MsgBox("Successfully logged off")
            btnSignIn.Text = "Sign In"
            btnSend.Enabled = False
        End If
    End Sub

    Public Sub Disconnect()
        '---Disconnect From The Server By Closing The Client
        Try
            client.GetStream.Close()
            client.Close()
        Catch ex As Exception
            'MessageBox.Show("Session Closed ")
        End Try
    End Sub

    [B]'---Delegate And Subroutine To Update The TextBox Control
    Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.Rtf &= str
    End Sub[/B]
    '--- Disconnect() method to Disconnect The Client From The Server And Close The Client 
    Private Sub Form1_FormClosing( _
       ByVal sender As Object, _
       ByVal e As System.Windows.Forms.FormClosingEventArgs) _
       Handles Me.FormClosing
        Disconnect()
    End Sub

When I rename it to txtMessageHistory.Text the total coding of the message is displayed. But when I changed it to txtMessageHistory.Rtf in the above code
the same message with codes is displyed in the server console but nothing is displayed in the RichTextBox (txtMessageHistory).
 
You can't append to Rtf property just like that. Lets say a rtf document has this format:
documentstart;full \green:document text;documentend
When you get Rtf/SelectedRTf you get a rtf object such as that, this example is an selection:
documentstart;ull \green:docu;documentend
If you add a rtf object to an existing like you did you get this malformed rtf text:
documentstart;full \green:document text;documentenddocumentstart;ull \green:docu;documentend
When this was what you really wanted:
documentstart;full \green:document textull \green:docu;documentend
JohnH said:
If you're not setting all RTF of the richtextbox in one go you perhaps just want to have another look in help, is there a property that allow you to get/set only part of text with formatting? Yes, there is one called SelectedRTF.
What this actually means is that to get/set only part of text with formatting you have to use the SelectedRtf property. When you do the RichTextBox class takes the existing Rtf object and merges it with the Rtf object you give to SelectedRtf property.

A big question now is where is the selection, and more importantly how to set the selection. When you want to append text you have to set selection to end of existing text. The RichTextBox class has a Select method that allow you to select, it takes two parameters; start index and selection length. Since you don't want to replace any existing text the selection length is easy, it's must be set to 0. What about start index? Start index must be equal to the length of text when you want to set selection start to end of text. To get this value you can use two methods, either get the Text first then use the String property Length, or simply use the TextLength property. After you have made the selection you can set SelectedRtf to in effect append formatted text to existing formatted text.
 
Well I could get you , what you said. but the problem I figured out is that
VB.NET:
'---Delegate And Subroutine To Update The TextBox Control
    Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.Text &= str
    End Sub
It is displaying the RTF tags and codes. I tried saving those with notepad and opened it in wordpad and found that they were exactly the same as I had formatted it in the RTB. Now only problem with my code is that, why isn't it able to convert those rtf tags to rtf format. I added a simple textbox(TextBox2) and changed the code written above to this.

VB.NET:
'---Delegate And Subroutine To Update The TextBox Control
    Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        TextBox2.Text += str
        Try
            txtMessageHistory.Rtf = TextBox2.Text
        Catch
            txtMessageHistory.Text = TextBox2.Text
        End Try
    End Sub
Here TextBox2.Text contains the valid rtf tags of the form

{\rtf1\ansi\ansicpg1252\deff0\deflang16393{\fonttb l{\f0\fnil\fcharset0 Microsoft Sans Serif;}}
\viewkind4\uc1\pard\i\f0\fs17 sfsfs\i0\par
}
When it tries to convert these rtf tags to Rich text by the command
txtMessageHistory.Rtf = TextBox2.Text
it fails and goes to the Catch part and displays the rtf tags. If I dont add a catch I dont get anything as the code is unable to convert the rtf tags in .Text to .Rtf . So is there any other way by which I will be able to achieve this. I just cant figure out where is the error.
 
If Rtf property don't accept the string you're offering it means it is not valid Rtf.

VB.NET:
Dim temp As String = Me.RichTextBox1.Rtf
'transfer temp string via TcpClient
Me.RichTextBox1.Rtf = temp
If temp string is the same on sender and receiver there can be no problem. So your problem must be sending the string. Maybe you should try the BinaryWriter/Reader ?
 
Changed the code to this
VB.NET:
 Private Sub btnSend_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnSend.Click
        Dim temp As String = Me.txtMessage.Rtf
        'transfer temp string via TcpClient
        Me.txtMessage.Rtf = temp
        SendMessage(temp)
        txtMessage.Clear()

    End Sub

Still to no avail. I tried out a simple project

VB.NET:
 Private Sub btnSend_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnSend.Click
        
        RichTextBox1.rtf = TextBox1.Text

    End Sub
and entered rtf tags in TextBox1. Same thing happened. No result. No conversion to rtf.

The statement itself doesn't work. I just desperately need a way to decode the rtf tags and display it in the richtextbox. I tried sending a msg with my original client and it popped in th rtf tags by the command
txtMessageHistory.Text &= str
but the txtMessageHistory.Rtf &= str doesnot work even if i changed the message sending code.

Just need a way to convert those rtf tags present in txtMessageHistory.Text to a .rtf format which can be displayed in the richtextbox.


And to be frank I do not know how Binary Reader/Writer works
 
anubhavmax said:
Still to no avail. I tried out a simple project

RichTextBox1.rtf = TextBox1.Text
That's nonsense. Here's a real science project for you. Add two RichTextBox controls and a Button control to a Form. Place this code in button click:
VB.NET:
RichTextBox2.Rtf = RichTextBox1.Rtf
Run the project and paste some rich text into the first box, just grab a piece from the VB code editor. Press the button and tell me the second box doesn't display the exact same rich text.
I just desperately need a way to decode the rtf tags and display it in the richtextbox.
All you need to do is get the rich text from sender box, convert it to bytes, transfer all the bytes, convert the bytes back to the very same rich text string, and assign it the receiver box.
And to be frank I do not know how Binary Reader/Writer works
I have shown you how the reader/writer work. There is nothing more to it than the two lines of code in post 4.
 
Sorry for being stupid...but I have been trying to achieve it all day long and I am still unable to do it. Please take a look at this and change the client/server side code so that it works out.Please..I really need to get this done. Please help me John...m relly to thankful and grateful to you.
 

Attachments

  • ServerClient.zip
    296.5 KB · Views: 31
Last edited by a moderator:
VB.NET:
Please take a look at this and change the client/server side code so that it works out.
Basically you haven't done any effort in accommodating your code so far other than changing "rtb.Text" to "rtb.Rtf", why would I? Now when you receive "10 bytes" you convert that to string and assign it Rtf property. Haven't you listen to me at all? When you send a rtf object, no matter how small or big it it, the receiver need to know how large it is. You have to tell the receiver how much data it must read, only then can the receiver be sure to read the exact number of data bytes, and only then can receiver decode it to a to fully complete single rtf string, and only then can the receiver assign it to the Rtf/SelectedRtf. There is a very simple logic to this that I have been trying to explain to you from the start, if you don't understand the logic you can't implement it in code, read the whole thread again and ask again if there is any part of it you don't understand.

The real science project part 2: replace the button code with code below, it will show how to transfer the rich string with BinaryWriter/Reader, a MemoryStream is used as stream to mimic the NetworkStream used in sockets.
VB.NET:
Dim stream As New IO.MemoryStream
Dim writer As New IO.BinaryWriter(stream)
writer.Write(Me.RichTextBox1.Rtf)
stream.Position = 0 '(enable the reader to read from start in this local stream)
Dim reader As New IO.BinaryReader(stream)
Me.RichTextBox2.Rtf = reader.ReadString
writer.Close()
reader.Close()
As you see, this example is no different than the first example I posted using BinaryWriter, and this example is no different in how the rich text is treated as a single complete string when assigning it to the Rtf/SelectedRtf property. There is no way you can cut this string in half and use it as a valid rtf string.
 
Well..thanks again..I hadn't thought about the bytes thing. I changed my SendMessage method code to
VB.NET:
Public Sub SendMessage(ByVal message As String)
        Try
            '---Send A Message To The Server
            Dim writer As New IO.BinaryWriter(client.GetStream)
            '---Send The Text
            writer.Write(message)
            writer.Flush()
        Catch ex As Exception
            MsgBox("Server Disconnected")

        End Try
    End Sub

but am unsure how to edit the receive message part which has the code

VB.NET:
Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        Try
            client.getstream.Position = 0
            Dim reader As New IO.BinaryReader(client.GetStream)
            Dim bytesRead As Integer
            bytesRead = client.GetStream.EndRead(ar)
            If bytesRead < 1 Then
                Exit Sub
            Else
                Dim para() As Object = _
                   {System.Text.Encoding.ASCII.GetString( _
                   data, 0, bytesRead)}
                Me.Invoke(New delUpdateHistory( _
                   AddressOf Me.UpdateHistory), para)
            End If
            client.GetStream.BeginRead( _
               data, 0, CInt(client.ReceiveBufferSize), _
               AddressOf ReceiveMessage, Nothing)
        Catch ex As Exception
        End Try
    End Sub

Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.Text &= str
    End Sub
tried to change it in a few ways but am ending up with error. But understood what you meant to say.

I tried to change the code this way.
VB.NET:
Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        Try

            Dim reader As New IO.BinaryReader(client.GetStream)
            Dim bytesRead As Integer
            bytesRead = client.GetStream.EndRead(ar)
            If bytesRead < 1 Then
                Exit Sub
            Else
                client.GetStream.Position = 0
                Dim para() As Object = _
                   {reader.ReadString}
                Me.Invoke(New delUpdateHistory( _
                   AddressOf Me.UpdateHistory), para)
                reader.Close()
            End If
            client.GetStream.BeginRead( _
               data, 0, CInt(client.ReceiveBufferSize), _
               AddressOf ReceiveMessage, Nothing)
        Catch ex As Exception
        End Try
    End Sub

Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.Text &= str
    End Sub

I made these changes in the client side. I am not sure but I guess server side doesnot require any changes. Can you say where I am mistaken in this process and where it is needed to be changed. Besides I am a bit confused where to add the writer.close( ) function.
 
Last edited:
Have a look at the The real science project part 2 again. What code is used to read the string? Is is just a reader.ReadString call? If this is difficult to grasp read the thread again, because I have explained that one before too.
 
I went thru the thread again
1) The sender side should inform the receiving side about the size of the rtf string in bytes that receiver side has to receive..and what I found is that sender side sent the info thru writer.write and receiver side received it by reader.ReadString( )
2) In order to read the whole string received
mstream.Seek(0, SeekOrigin.Begin)
or what you wrote
client.Getstream.Position=0
which I did embed in the code.

Now I couldnot understand what else do I need to change in here.
And does this part of the code needs any change.

VB.NET:
Public Delegate Sub delUpdateHistory(ByVal str As String)
    Public Sub UpdateHistory(ByVal str As String)
        txtMessageHistory.Rtf &= str
    End Sub
Please be specific and to the point. I really appreciate your effort to make me understand what this is all about but some of your statements are mysterious to me. I am just starting with VB.net and cannot get everything into my head at the first go.Pls bear with me.
Thanks again.
 
Back
Top