none
How do I write text to an RTF Text Box

    Question

  • I'm adapting the UsingMenusStatusStripsToolStrips example (in CS101 samples) which puts some example text in an RTF box and allows a user to type in the box. Instead I have a class that receives text via an IP socket and I want to place this text in the box. I tried adding a public "WriteToForm" method in Form1.cs, but code in the IP listner object can't access this method. Is there a trick to this?

    Thanks

    Scott

    Tuesday, August 15, 2006 8:15 AM

Answers

  • I think you might be confused as to what the public modifier on the delegate means.  It is simply the type of the delegate that you are declaring, not an actual instance (like class vs. object).  Since the updatertf method is private to MainForm, the only place that you can refer to it directly  is inside of MainForm.  You need to modify the ParseCalc constructor to take a parameter of type UpdateRTFDelegate. 

    The second error you are getting is because MainForm is the name of the class, not the name of your main form object itself.  To fix this, consider calling the delegate directly from the ParseCalc thread, and have the updatertf method perform the Invoking on the control.

    Maybe this will help illustrate what I mean:

    public delegate void UpdateRTFDelegate(string str);

    public class MainForm : Form
    {
        ParseCalc parser;
        UpdateRTFDelegate updatertfCallback; // this is just to prevent creating a new delegate every time new data is received

        public MainForm()
        {
           updatertfCallback = new UpdateRTFDelegate(this.updatertf);
           parser = new ParseCalc(updatertfCallback);
        }

        public MainForm_Load()
        {
           parser.BeginListening(); // whatever you called this method that starts the thread
        }

        private void updatertf(string str)
        {
           if (displayArea.InvokeRequired)
           { 
              // call this same method on the correct thread
              displayArea.Invoke(uptatertfCallback, new object[1] { str });
           }
           else
           {
              // now we're on the control's thread
              displayArea.Rtf = @"{\rtf1\ansi \b " + s + @"}";
           }
        }
    }

    public class ParseCalc
    {
        private UpdateRTFDelegate rtfCallback;
        public ParseCalc(UpdateRTFDelegate callback)
        { 
           rtfCallback = callback;
        }

        private void dataReceived(string str)
        {
           // ... processing, etc...
           if (rtfCallback != null)
              rtfCallback(str);
        }
    }
    Tuesday, August 22, 2006 4:26 AM

All replies

  • hmmm you would have to read the bytes[] into a string:

     

    string theStringRecieved = System.Text.Encoding.ASCII.GetString(bytesFromSocket);

     

    then place it to the rtf control:

     

    this.theRichTextControl.Text += theStringRecieved; //append to existing data in the RTF

     

    does this help?

    Tuesday, August 15, 2006 10:33 AM
    Moderator
  • I get the bytes into a string ok. It is the last bit - sending the text to the control - that is causing problems because they are in separate objects. In the listner object I have:

    MainForm.Form_write(theReceivedString);

    In the main form, which holds the rich text box I have:

    internal static void Form_write(string s) {  //gives error in calling line without the static modifier

    displayArea.Rtf = @"{\rtf1\ansi \b " + s + @"}";  //gives object reference required error

    }

    If I use "this.displayArea..." then it says "this" is not valid in a static method.

    Tuesday, August 15, 2006 9:52 PM
  • well since its a static method then yes, "this" will not apply so remove it and leave the rest in.

    Could I ask why you are making things static? what other errors, if any, are you recieving? Where?

    Wednesday, August 16, 2006 12:19 AM
    Moderator
  • Because I am calling this from another object (class). I've created my IP listner object in a separate thread as it just listens for and processes messages. That's works fine.

    If I remove "static" from the above Form_write method, I get an error at the original call to Form_write - "An object reference is required for the nonstatic field, method, or property 'CalcDisplay.MainForm.Form_write(string)'"

    MainForm.Form_write(theReceivedString); //error here if called method is nonstatic

    All code is in the same namespace "CalcDisplay". Adding the namespace in front of the above call does not help. There are no other errors. I think the problem is that the main form (Form1) is instantiated in Main() - "Application.Run(new MainForm());" without an actual object reference. [As an old 'C' programmer I feel I need to add some globals ]

    Wednesday, August 16, 2006 12:48 AM
  • The problem is that it is possible for you to have more than one form, and so the listener thread can't automatically know which form to send the data to. You have a few options:

    1) Use the singleton pattern for your form. This basically allows you to get the unique reference to your form inside a static context.  This solution is sort of like adding a global.

    2) Make your IP Listener expose an event like RtfTextReceived. Then, if you create the Listener object inside MainForm, bind the event to a non-static method that writes the text to the text box.

    3) Pass the Listener object a reference to your form when you construct it. When the data is received, call the non-static public method inside the form. A similar solution that is somewhat better would be creating an IRtfListener interface and having the form implement it. Then the Listener wouldn't be as tightly coupled to MainForm.

    I would personally go with 2, but 3 could be good if you were going to have lots of different types of incoming data.

    Hope it helps.

    Wednesday, August 16, 2006 1:43 AM
  • Are you getting an exception regarding something about "Cross-thread"?

    I had a similar problem earlier with my application because I was using asynchronous sockets and callback methods. Since the callback methods uses threads from the thread-pool, this was the only way to go since I was looking for scalability.

    what you have to do is set up a delegate on your main form that accepts a string as a parameter. Then you have to create an instance of this delegate with the handling method as the parameter. The prototype of the handling method must match that of the delegate. This instance should be created in the callback method of your asynchronous socket. So when the socket calls the callback method, the instanace is created and passed to it is the string to append. To actually use this delegate method, you have to use Control.Invoke() or Control.BeginInvoke().

    for example:

    // class level - just inside your namespace
    public delegate void UpdateRTFDelegate(string str);


    // inside callback method
    string theStringToAppend = "whatever";
    UpdateRTFDelegate urd = new UpdateRTFDelegate(updatertf);
    RTFTextBox.Invoke(urd, new object[] { theStringToAppend });


    // updatertf method
    private void updatertf(string str)
    {

    RTFTextBox.AppendText(str);

    }

    Since you are trying to manipulate an object from another thread other than the thread that created it, you have to use the Invoke method of that object. This ensures the proper context of that object is loaded before the method updatertf called - and subsequently changes are made to the object RTFTextBox.

    hope this helps.

    Wednesday, August 16, 2006 10:30 AM
  • I think a delegate is the way to go if I can figure it out, as text display is initiated from a separate thread. I still have to pass the delegate reference from the main form to the asynch socket listner object don't I so it can indirectly reference the display method?

    Thursday, August 17, 2006 2:56 AM
  • I'm not quite sure what you are saying... but let me clarify my example above.

    The reason I created the delegate object in the class level (just inside the namespace) is so that any class sharing the same namespace will have sufficient access to this delegate. Similar to creating your own events, if the delegate of the event is created at the class level, then any class within this namespace will be able to handle the event (ie. += ) or raise the event.

    From what you say above, you seem to be trying to "pass the delegate reference from the main form..." to your callback method. Well - as stated in the comments in my example above, I don't actually instantiate a refrence to this delegate until I'm actually inside the callback method. This way, there is nothing to pass between the main form and the callback method. Since the delegate was created on the class level, both the main form (method) and the callback method should have sufficient access to this (public) delegate.

    Note however, (re: updatertf method) the handling-method should be placed within the same class as the RTF control. This way the handling-method will have sufficient access to the control instance. Then all you have to do is call the Invoke method of a control of which you have sufficient access, and also of which the thread the RTF control was created from. Essentially, you can call RTFTextBox.Invoke, or MainClass.Invoke. As long as RTFTextBox was instantiated within MainClass, then calling the invoke from MainClass is the same as calling invoke from RTFTextBox. It all depends what you have sufficient access to.

    As long as you can get to a control, all which matters after that - are the paramenters of the Invoke method. The first parameter (ie. urd) is the delegate type, and the second paramenter is an array of objects used as parameter to invoke the method instantiated in the first parameter's delegate instance.

    I hope this helps. If you're still have trouble, try posting some sample code.

    Monday, August 21, 2006 6:38 AM
  • I haven't got this right yet as I still get errors. I have a MainForm object which is the main application, and a ParseCalc object which runs under the asynchronous thread that gets text via the socket.

    At the class level in the main application, before the  "public partial class MainForm : Form" is defined, I have:

    public delegate void UpdateRTFDelegate(string str);  // top level delegate

    This delegate is visible from ParseCalc and other objects in the same namespace - no problem. In the same file, within the MainForm object, I have code as follows to output text. "displayArea" is the name of the rich text box on the main form.

    private void updatertf(string s) {

    displayArea.Rtf = @"{\rtf1\ansi \b " + s + @"}";

    }

    In the ParseCalc object I've put what I think you refer to as the callback method:

    private void writeRTF(string s) {

    UpdateRTFDelegate urd = new UpdateRTFDelegate(updatertf); //error 1

    MainForm.Invoke(urd, new object[] { s }); //error 2

    }

    The two errors are

    Error 1 The name 'updatertf' does not exist in the current context 
    Error 2 An object reference is required for the nonstatic field, method, or property 'System.Windows.Forms.Control.Invoke(System.Delegate, params object[])'

    I tried other combinations but one or other of the methods are not visable.

    Thanks.

    Tuesday, August 22, 2006 2:59 AM
  • I think you might be confused as to what the public modifier on the delegate means.  It is simply the type of the delegate that you are declaring, not an actual instance (like class vs. object).  Since the updatertf method is private to MainForm, the only place that you can refer to it directly  is inside of MainForm.  You need to modify the ParseCalc constructor to take a parameter of type UpdateRTFDelegate. 

    The second error you are getting is because MainForm is the name of the class, not the name of your main form object itself.  To fix this, consider calling the delegate directly from the ParseCalc thread, and have the updatertf method perform the Invoking on the control.

    Maybe this will help illustrate what I mean:

    public delegate void UpdateRTFDelegate(string str);

    public class MainForm : Form
    {
        ParseCalc parser;
        UpdateRTFDelegate updatertfCallback; // this is just to prevent creating a new delegate every time new data is received

        public MainForm()
        {
           updatertfCallback = new UpdateRTFDelegate(this.updatertf);
           parser = new ParseCalc(updatertfCallback);
        }

        public MainForm_Load()
        {
           parser.BeginListening(); // whatever you called this method that starts the thread
        }

        private void updatertf(string str)
        {
           if (displayArea.InvokeRequired)
           { 
              // call this same method on the correct thread
              displayArea.Invoke(uptatertfCallback, new object[1] { str });
           }
           else
           {
              // now we're on the control's thread
              displayArea.Rtf = @"{\rtf1\ansi \b " + s + @"}";
           }
        }
    }

    public class ParseCalc
    {
        private UpdateRTFDelegate rtfCallback;
        public ParseCalc(UpdateRTFDelegate callback)
        { 
           rtfCallback = callback;
        }

        private void dataReceived(string str)
        {
           // ... processing, etc...
           if (rtfCallback != null)
              rtfCallback(str);
        }
    }
    Tuesday, August 22, 2006 4:26 AM
  • Thanks. I had to adjust it a bit to suit my structure but it compiles and puts up a test message. Now I have to get the real messages displayed.
    Tuesday, August 22, 2006 5:33 AM