Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent Autoscrolling in RichTextBox

I have a readonly data logging window that I implemented using the RichTextBox control. I'd like to be able to disable the autoscrolling that happens when the user clicks in the control so that the user can select a specific log for copy/paste operations or whatever. However, as soon as the user clicks in the RichTextBox, it automatically scrolls to the bottom, making this difficult.

Anyone know a way to override this behavior?

Thanks!

like image 367
goombaloon Avatar asked Mar 09 '09 16:03

goombaloon


3 Answers

The RichTextBox control automatically scrolls to the current selection, if the selection is not hidden. RichTextBox.AppendText(), in addition to appending text, also modifies the current selection, and so indirectly triggers the "autoscrolling" behaviour. Note that if RichTextBox.HideSelection is set to true, then the selection would be hidden when the control is not in focus; this explains the behaviour you described, where autoscrolling occurs only when the user clicks in the control. (thereby giving it focus) To prevent this, you need to do the following when appending text:

  1. Backup the initial selection
  2. Unfocus the control
  3. Hide selection (through a Windows message)
  4. AppendText
  5. Restore the initial selection
  6. Unhide selection
  7. Refocus the control

You may also want to check whether the selection is already at the end of the text, and allow the autoscrolling behaviour if it is - this essentially emulates the behaviour of Visual Studio's Output window. For example:

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
    const int WM_USER = 0x400;
    const int EM_HIDESELECTION = WM_USER + 63;

    void OnAppend(string text)
    {
        bool focused = richTextBox1.Focused;
        //backup initial selection
        int selection = richTextBox1.SelectionStart;
        int length = richTextBox1.SelectionLength;
        //allow autoscroll if selection is at end of text
        bool autoscroll = (selection==richTextBox1.Text.Length);

        if (!autoscroll)
        {
            //shift focus from RichTextBox to some other control
            if (focused) textBox1.Focus();
            //hide selection
            SendMessage(richTextBox1.Handle, EM_HIDESELECTION, 1, 0);
        }

        richTextBox1.AppendText(text);

        if (!autoscroll)
        {
            //restore initial selection
            richTextBox1.SelectionStart = selection;
            richTextBox1.SelectionLength = length;
            //unhide selection
            SendMessage(richTextBox1.Handle, EM_HIDESELECTION, 0, 0);
            //restore focus to RichTextBox
            if(focused) richTextBox1.Focus();
        }
    }
like image 107
SytS Avatar answered Nov 12 '22 18:11

SytS


You might take a look at doing something like this:

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr LockWindowUpdate(IntPtr Handle);

then in your method that appends log data (I'm making some assumptions here) you might do something like this:

LockWindowUpdate(this.Handle);
int pos = richTextBox1.SelectionStart;
int len = richTextBox1.SelectionLength;
richTextBox1.AppendText(yourText);
richTextBox1.SelectionStart = pos;
richTextBox1.SelectionLength = len;
LockWindowUpdate(IntPtr.Zero);

I did a little test app with a timer that did the append on the richtextbox and it stopped it from scrolling so I could do the text selection. It has some positional issues and isn't perfect, but perhaps it'll help move you toward a solution of your own.

All the best!

like image 26
itsmatt Avatar answered Nov 12 '22 18:11

itsmatt


SytS's solution has an issue, when some text is "appended", the scroll bar moves such that the selection go to the top of the panel. A solution is to save/restore the scroll position with :

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    extern static int GetScrollPos(IntPtr hWnd, int nBar);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);

This solution is more complete for me.

like image 1
Orace Avatar answered Nov 12 '22 17:11

Orace