Click here to Skip to main content
Click here to Skip to main content

How to: C# - Prevent RichTextBox from auto scrolling when using AppendText

, 5 Apr 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Prevent a RichTextBox control from automatically scrolling to the bottom when appending text.

Introduction

Having spent many days and nights searching for a solution to my specific problem: How to append text to a RichTextBox control without it automatically scrolling to the bottom of the control, I couldn't find anything that was good for me - I even found a reference to someone saying that "People have been asking how to do this for over 5 years now and no one has come up with a (good) solution." Well... I don't know if this qualifies as a "good" solution, but it certainly fulfils my requirements Smile | :)

Background

I have been developing an external chat interface for a popular set of MMORPGs, whereby people will be able to talk to their in-game friends but with the game client minimised. It will eventually enable multi-tabbed chat in a similar fashion to the popular instant messaging programs such as MSN and Yahoo! Having got everything looking nice, there was still one problem seriously plaguing my development... As my program is designed to allow a user to see messages which have been posted way earlier than the messages that were still present in the in-game chat interface (limited to 200 messages), when a user scrolls up to view earlier messages, the arrival of a new message causing the entire control to scroll right back to the bottom was a major annoyance.

I had tried all sorts of things such as logging the current scroll position and then trying to scroll back to there when the auto-scroll stuff happened, but that resulted in undesirable 'flapping around' of the text content.

Another solution suggested temporarily setting the focus to another control, appending the text to the main RichTextBox, then returning focus to the RTB (the auto-scrolling doesn't happen if the RichTextBox doesn't have focus). However, this was just unbearable to look at once there was sufficient text content to really slow everything down.

I even tried implementing the chat interface in a WebBrowser control which alleviated the auto-scroll nightmare, but seriously restricted my other options. Then... I decided to delve deeper into the WndProc message system and managed to single out the culprit for the auto-trolling problem - WM_SETFOCUS.

Using the Code

The solution I have arrived at is effectively an inherited RichTextBox class which disables the WndProc message that triggers the RTB control to gain focus, then simulates some of the functionality subsequently lost due to disabling it.

protected override void WndProc(ref Message m)
{
    // Let everything through except for the WM_SETFOCUS message
    if(m.Msg != WM_SETFOCUS)
        base.WndProc(ref m);
}

There are of course a few drawbacks to this... If there is no focus, text can not be selected, copied, etc. To regain this lost functionality, some event handlers need to be added to simulate the original disabled functions. Well... this example includes a few functions which allow the user to select text again. Copying text to the clipboard can be achieved by adding event handlers to the various key event handlers (an example of copying to clipboard is shown later).

So, here is my new MyRichTextBox class!

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Forms;

class MyRichTextBox : RichTextBox
{
    public bool mouseDown;
    public int selCurrent;
    public int selOrigin;
    public int selStart;
    public int selEnd;
    public int selTrough;  
    public int selPeak;
    private Color defaultBackColour;

    private int WM_SETFOCUS = 0x0007;
    private UInt32 EM_SETSEL = 0x00B1;
        
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

    public MyRichTextBox()
    {
        this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.rtb_MouseDown);
        this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.rtb_MouseMove);
        this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.rtb_MouseUp);
        defaultBackColour = this.BackColor;
    }
        
    protected override void WndProc(ref Message m)
    {
        // Let everything through except for the WM_SETFOCUS message
        if(m.Msg != WM_SETFOCUS)
            base.WndProc(ref m);
    }

    public void rtb_MouseDown(object sender, MouseEventArgs e)
    {
        mouseDown = true;
        // reset all the selection stuff
        selOrigin = selStart = selEnd = selPeak = selTrough = GetCharIndexFromPosition(e.Location);
        highlightSelection(1, Text.Length - 1, false);
    }

    public void rtb_MouseUp(object sender, MouseEventArgs e)
    {
        mouseDown = false;
    }

    public void rtb_MouseMove(object sender, MouseEventArgs e)
    {
        if (mouseDown)
        {
            selCurrent = GetCharIndexFromPosition(e.Location);

            // First determine the selection direction
            // Note the +1 when selecting backwards because GetCharIndexFromPosition
            // uses the left edge of the nearest character
            if (selCurrent < selOrigin + 1)
            {
                // If the current selection is smaller than the previous selection,
                // recolour the now unselected stuff back to the default colour
                if (selCurrent > selTrough)
                {
                    highlightSelection(selTrough, selCurrent, false);
                }
                selTrough = selCurrent;
                selEnd = selOrigin + 1;
                selStart = selCurrent;
            }
            else
            {
                // If the current selection is smaller than the previous selection,
                // recolour the now unselected stuff back to the default colour
                if (selCurrent < selPeak)
                {
                    highlightSelection(selPeak, selCurrent, false);
                }
                selPeak = selCurrent;
                selStart = selOrigin;
                selEnd = selCurrent;
            }

            highlightSelection(selStart, selEnd);
        }
    }

    private void highlightSelection(int start, int end, bool highlight = true)
    {
        selectText(start, end);
        SelectionBackColor = highlight ? Color.LightBlue : defaultBackColour;
    }

    private void selectText(int start, int end)
    {
        SendMessage(Handle, EM_SETSEL, start, end);
    }
}

Points of Interest

Of course, there are various other functionalities that are lost due to disabling the WM_SETFOCUS message, for example, as previously mentioned, the ability to copy text to the clipboard. However, this can simply be fixed by adding an event handler to your main form:

private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
    if (e.KeyChar == 0x03)   // ctrl + C
    {
        Clipboard.SetText(rtb1.Text.Substring(rtb1.selStart, rtb1.selEnd - rtb1.selStart));
    }
}

For anything else, I'm afraid you're on your own, but hopefully if you've found your way to this article, this will give you a nice starting point.

I make no claims of being a professional programmer... I just dabble in my spare time, so please go easy on me for my first article Smile | :)

History

  • 5 April 2012 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Stuart McConnel

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
QuestionControlling? PinmemberMember 1076239013-Oct-14 5:47 
GeneralMy vote of 5 PinmemberDARlas22-Sep-12 8:18 
GeneralNice piece of work and well explained. Pinmemberrvp717y21-Apr-12 2:15 
QuestionWould this be better as a tip PinmemberSlacker0075-Apr-12 10:04 
AnswerRe: Would this be better as a tip PinmemberStuart McConnel8-Apr-12 19:23 
AnswerRe: Would this be better as a tip PinmemberMember 841302010-Apr-12 23:26 
GeneralRe: Would this be better as a tip PinmemberSlacker00711-Apr-12 0:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 5 Apr 2012
Article Copyright 2012 by Stuart McConnel
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid