3

I want to get the sub-string under cursor in a RichTextBox.

private void richTextBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Clicks == 1 && e.Button == MouseButtons.Left)
    {
        string wholeText = richTextBox1.Text;
        // Obtain the character index where the user clicks on the control. 
        int positionBegin = richTextBox1.GetCharIndexFromPosition(new Point(e.X, e.Y));
        SelectedText = wholeText.Substring(positionBegin);
    }

For example, if I type the string World then place the cursor between l and d, the sub-string should be d. Till now my code is working, however if I place the cursor at the end of the string, it still returns d.

I expect that it will return the empty then in this case. It looks like a bug. See http://www.pcreview.co.uk/forums/problem-getcharindexfromposition-t4037504.html

3 Answers3

4

How is it a bug? If you look at the discription, it gets the closest character. The closest character to the position you click is indeed the last letter. I haven't seen the source, but based on the description, it is working as intended.

In my brief look at the methods available to the RichTextBox I didn't see one that would resolve the issue easily. Something you could do is pad the ending of the text with an actual character, eg: a space.

//...
string originalText = richTextBox1.Text;
richTextBox1.Text += " ";

//Your code to get the index and substring

//Return the textbox to its original state
richTextBox1.Text = originalText;
richTextBox1.SelectionLength = 0;
richTextBox1.SelectionStart = positionBegin;

That should do what you are looking for, but it is rather simple and will be inefficient depending on the amount of text you're dealing with. If you wanted to create a method that functions like the GetCharIndexFromPosition(Point pt), you will probably end up in the unmanaged code realm.


EDIT: Because I was curious (Extension method solution)

I started looking at the source code of the RichTextBox control and it does indeed stop you from selecting a character outside the max length of the text length as seen here:

public override int GetCharIndexFromPosition(Point pt) {
    NativeMethods.POINT wpt = new NativeMethods.POINT(pt.X, pt.Y);
    int index = (int)UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.EM_CHARFROMPOS, 0, wpt);

    string t = this.Text;
    //This bit is stopping you from getting an index outside the length of the text
    if (index >= t.Length) {
        index = Math.Max(t.Length - 1, 0);
    }
    return index;
}

To "fix" this you can make a extension method that implements it the same way but remove the bit about checking to see if the index is larger than the length of the text:

public static class RichTextBoxExtensions
{
    private const int EM_CHARFROMPOS = 0x00D7;

    public static int GetTrueIndexPositionFromPoint(this RichTextBox rtb, Point pt)
    {
        POINT wpt = new POINT(pt.X, pt.Y);
        int index = (int)SendMessage(new HandleRef(rtb, rtb.Handle), EM_CHARFROMPOS, 0, wpt);

        return index;
    }

    [DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, POINT lParam);
}

[StructLayout(LayoutKind.Sequential)]
internal class POINT
{
    public int x;
    public int y;

    public POINT()
    {
    }

    public POINT(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

With this you are back to your original code! Just change GetCharIndexFromPosition to GetTrueIndexPositionFromPoint and it will work as expected.

Community
  • 1
  • 1
Justin
  • 3,337
  • 3
  • 16
  • 27
  • @Love Check out my updated. This should suit your needs better than manipulating the `RichTextBox` control so much. – Justin Jul 10 '14 at 21:22
0

Another (simple) solution.
I have the same exact issue, only in my case the rich text box is below a custom transparent panel and upon clicking on the transparent panel I have to place the cursor in the right position within rich text box. Here is a solution that I decided to use because all others seemed more complex. Simply get the point of the last character in you rich text box. Then compare it to where you clicked and based on comparison results make the appropriate adjustments. Look at code I used for my application. Pay attention to 'GetCharIndexFromPosition()' and 'GetPositionFromCharIndex()'. I hope this helps next person or at least triggers their creativity to come up with a more clever solution.

private void transparentPanel_Click(object sender, System.EventArgs e)
        {
            var mouseEventArgs = e as MouseEventArgs;
            if (mouseEventArgs.Button == MouseButtons.Left)
            {
                // If currently in text editing mode
                if (m_currentSelectedControl == e_SelectedControl.TEXT)
                {
                    Point capturedPoint = new Point(mouseEventArgs.X, mouseEventArgs.Y);
                    int charIndex = richTextBox.GetCharIndexFromPosition(capturedPoint);
                    int lastChar = richTextBox.TextLength;
                    Point lastCharPoint = richTextBox.GetPositionFromCharIndex(lastChar);

                    if (lastCharPoint.X == capturedPoint.X ||
                        lastCharPoint.Y < capturedPoint.Y)
                    {
                        charIndex++;
                    }

                    richTextBox.SelectionStart = charIndex;
                    richTextBox.SelectionLength = 0;
                    richTextBox.Select();
                    transparentPanel.Refresh();
                }
            }
        }
Murat Zazi
  • 347
  • 4
  • 10
0

This is a known documented behavior. The Remarks section of the GetCharIndexFromPosition method documentation contains the following Important note:

If the specified location is not within the client rectangle of the control, or is beyond the last character in the control, the return value is the index of the last character.

venkatesan r
  • 249
  • 3
  • 15