2

I've been running into a kinda frustrating roadblock with RichTextBox.ScrollToCaret. I have code that prints messages to a RichTextBox. When each message is sent to the form, it is split into multiple lines and formatted, then each line is concatenated and the result is sent to RichTextBox.Append. Then, the following two calls are made to scroll to the bottom of the box:

outputBox.Select(outputBox.Text.Length, 0);
outputBox.ScrollToCaret();

When printing one message, it's fine. When printing a small handful of messages, no problems. When printing a bunch of messages in quick succession, it will randomly (how many messages it prints before it happens) throw an AccessViolationException ("Attempted to read or write protected memory. This is often an indication that other memory is corrupt.", full details here) the next time Append is called on that box to add the next message. This only happens when doing it in quick succession and only when using RichTextBox.ScrollToCaret each time. The following code that I fell back on works fine:

outputBox.Focus();
outputBox.Select(outputBox.Text.Length, 0);

I found out too that even if I caught the exception and threw it away, the program will hang on the next invocation of Append. So, I assume it's a problem with the actual code in RichTextBox. Anyone have any ideas?

I can post more of my code if anyone needs it but the situation really is pretty basic. A few things of note is that there is no multithreading (other than the inherent UI thread), so the object sending the messages and the form receiving them are on the same thread. Also, this is under .NET 4.0.

I found this other question addressing this issue but only a workaround was provided, no real explanation: AccessViolation occurs in RichTextBox.ScrollToCaret. My experience with threading is unfortunately not where I'd want so I wasn't able to get their solution working properly, but luckily what I posted above works just fine.

Update 1

So it looks after some testing like it has something to do with XNA, so it may be my misunderstanding of how that works with threading. I have been unable to reproduce the error in a pure WinForms app, but easily accomplished it with a simple XNA game. I have zipped both here for you to look at. Apologies for the error. https://dl.dropbox.com/u/16985121/StackOverFlowExamples.zip

Community
  • 1
  • 1
Mr.Hotkeys
  • 33
  • 8
  • is the .Select() call invoked on a different thread than the .Append calls ? – cppanda Dec 25 '12 at 04:09
  • Nope, everything I wrote occurs on the main thread. So there's just that and the UI thread. – Mr.Hotkeys Dec 25 '12 at 04:50
  • public delegate void WriteLogEntryDelegate(string log_entry); void WriteLogEntryCB(string log_entry) { if (richTextBox1.InvokeRequired == true) { var d = new WriteLogEntryDelegate(WriteLogEntryCB); this.Invoke(d, log_entry); } else { richTextBox1.AppendText(log_entry + "\r\n"); this.richTextBox1.SelectionStart = this.richTextBox1.Text.Length; this.richTextBox1.ScrollToCaret(); } } – John Bartels Dec 25 '12 at 05:30
  • Tried just calling it, tried invoking a delegate instance tied to that method, and tried invoking a delegate instance on the form, all to no avail. I'm trying to work up an example at the moment to upload for you guys to pick apart. – Mr.Hotkeys Dec 25 '12 at 05:44
  • I've updated the main post to reflect my tests. Hopefully XNA isn't a dealbreaker for you guys, but if it is, thanks anyways! – Mr.Hotkeys Dec 25 '12 at 06:04
  • Yes, I am able to replicate this and have confirmed similar issues to what you report as well. This is NOT XNA related. It is internal to the .NET framework and handling of the RichTextBox.AppendText() after successions of RichTextBox.ScrollToCarrret() OR ScrollToBottom() Win32 call as mentioned. Confirmed with .NET v4.5.2 – Latency Nov 01 '21 at 18:52

3 Answers3

1

I had same issue. I have little different situation but basically same problem. I have mixed up code with C++/CLI and C# forms.

One of thread from C++/CLI calls function in C# form to print out message on to richtextbox.

Calling this function 'slowly' is OK. But if the calling function happens very fast and often, program randomly crashed.

Here is my code.

void PrintOutLog(System::String^ s)
    {
        Monitor::Enter(this->richTextBox_LogBox);
        try
        {
            if(this->richTextBox_LogBox->InvokeRequired)
            {

                AddListItem^ d = gcnew AddListItem(this, &PrintOutLog);
                array<Object^>^ myStringArray = {s};
                this->richTextBox_LogBox->BeginInvoke(d, myStringArray);
            }
            else
            {                
                this->richTextBox_LogBox->AppendText(s + "\n");
                this->richTextBox_LogBox->SelectionStart = this->richTextBox_LogBox->Text->Length;
                this->richTextBox_LogBox->ScrollToCaret();
            }

        }
        finally
        {
            Monitor::Exit(this->richTextBox_LogBox);
        }

    }

It turns out if I comment out following two lines of code, the program does not crash any more due to memory access violation.

this->richTextBox_LogBox->SelectionStart = this->richTextBox_LogBox->Text->Length;
this->richTextBox_LogBox->ScrollToCaret();

If I comment those two lines out, then the richtextbox does not show the new log message at the end of text box when the C# form does not have focus.

I could use your solution which is getting focus before putting text but if I do that, it always stay on top of other windows that I need to keep stay on top most. So I could not do that.

I looked up MSDN page http://msdn.microsoft.com/en-us/library/system.windows.forms.textboxbase.scrolltocaret.aspx and found in the middle of page saying this.

This method has no effect if the control does not have focus or if the caret is already positioned in the visible region of the control.

But I believe this is not true. It seems like when ScrollToCaret() is called when the focus is not on the richtextbox control, I can see the scroll bar of richtextbox moves down when it gets new messages which means it printed message and updated even it did not have focus.

I tried to lock the richtextbox to be safe from multi-threading but it did not solve the access violation problem. It will be great if there is another solution to fix this problem other than using focus() function.

Thanks.

diehard98
  • 232
  • 4
  • 14
  • 1
    I'm glad I'm not the only one that was having this, was driving me crazy. Since I posted this, I found this solution to circumvent the problem that works totally fine for me and hopefully for you as well. http://stackoverflow.com/a/8562457/568042 – Mr.Hotkeys Apr 15 '13 at 16:52
  • Great! Thank you very much. I will try to use it and I am sure it will work for me too because we had identical problem. :) – diehard98 Apr 18 '13 at 18:50
  • I modified code based on your link and it really works. I may need to test with few other setting but I guess it works fine. One different thing for me was setting up WM_VSCROLL and SB_PAGEBOTTOM little differently because my application already defined it on C++ side of code. So, it seems like I need to define them with little different name but it has same values. Thank you again. – diehard98 Apr 24 '13 at 13:55
  • I just noticed one of my problem in the code that I put above. When I call the PrintOutLog() very fast in other thread, it will get out of memory error in runtime because BeginInvoke is not waiting to finish the previous invoke work. So, pretty much the other thread keeps calling infinitely the PrintOutLog() function and eventually the program gets out of memory error. To avoid this, I have to use Invoke() instead of BeginInvoke(). – diehard98 May 06 '13 at 19:30
1

Found another question here which gave me a way to circumvent the issue entirely to produce the same output without any problems: https://stackoverflow.com/a/8562457/568042

Community
  • 1
  • 1
Mr.Hotkeys
  • 33
  • 8
0
public delegate void WriteLogEntryDelegate(string log_entry);

    void WriteLogEntryCB(string log_entry)
    {
        if (richTextBox1.InvokeRequired == true)
        {
            var d = new WriteLogEntryDelegate(WriteLogEntryCB);
            this.Invoke(d, log_entry);
        }
        else
        {
            richTextBox1.AppendText(log_entry + "\r\n");
            this.richTextBox1.SelectionStart = this.richTextBox1.Text.Length;
            this.richTextBox1.ScrollToCaret();
        }
    }
John Bartels
  • 2,583
  • 3
  • 19
  • 26
  • As I said in the comments above, unfortunately this code did not resolve the situation. It seems to make the exceptions less frequent, though I can't say that for sure. It does, however, change it so that sometimes instead of throwing an AccessViolatedException, it'll just hang on the call to AppendText indefinitely, with VS2010 telling me there's a native frame on top of the call stack. – Mr.Hotkeys Dec 25 '12 at 07:45