Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

programmatically move caret in textbox, line up and line down

I am struggling to move the caret in a textbox editing control within a DataGridView, one line up and one line down, just as a user would get when pressing up and down arrows.

So I don't mean lines as what is between newline characters, I mean lines as what is between the left and right side of a textbox.

I cannot use GetCharIndexFromPosition and GetPositionFromCharIndex because not all text will always be shown in the textbox display area.

Edit: I cannot simulate KeyPress because I am dealing with a textbox cell within a DataGridView. My aim is in fact getting arrow keys to do what they would do in a normal textbox, instead of jumping from row to row.

like image 848
Dil Avatar asked Nov 04 '22 21:11

Dil


2 Answers

This should work.

Point pOld = textBox1.GetPositionFromCharIndex(textBox1.SelectionStart);
Point pNew = new Point(pOld.X, pOld.Y + textBox1.Font.Height)
int charIndex = textBox1.GetCharIndexFromPosition(pNew);
textBox1.SelectionStart = charIndex;

I don't think it's the cleanest solution though. Maybe you should look into the DataGridView properties/key handling.

like image 193
Jacco Krijnen Avatar answered Nov 15 '22 00:11

Jacco Krijnen


The methods GetPositionFromCharIndex() and GetCharIndexFromPosition() have two limitations:

  1. they don't work for text beyond the borders of the textbox
  2. The character index of TextBox.SelectionStart is the same for a caret at the end of a line and for a caret at the beginning of next line.

To correct this, you can:

  1. scroll the textbox to show the relevant line before using the said methods.
  2. use GetCaretPos function from user32.dll to compare it with the position of SelectionStart. If they are not equal, it means that caret is at the end of line.
  3. simulate {END} key press to position caret at the end of a line.

Another problem I encountered is that TextBox.Lines refers to logic lines separated by new-line characters, while functions TextBox.GetLineFromCharIndex() and TextBox.GetFirstCharIndexFromLine() refer to visual lines as they are displayed in the textbox (that is, from side to side of TextBox, without there having to be new-line characters). Do not mix them up.

Resulting code (ugly as you may claim, but working) is as follows:

class Utils
{
    [DllImport("user32.dll")]
    static extern bool GetCaretPos(out System.Drawing.Point lpPoint);

    public static void LineUp(TextBox tb)
    {
        int oldCharIndex = tb.SelectionStart;
        int oldLineNo = tb.GetLineFromCharIndex(oldCharIndex);
        System.Drawing.Point oldCharPos = tb.GetPositionFromCharIndex(oldCharIndex);
        System.Drawing.Point oldCaretPos;
        if (GetCaretPos(out oldCaretPos))
        {
            if (oldCharPos == oldCaretPos)
            {
                if (oldLineNo > 0)
                {
                    tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 1);
                    tb.ScrollToCaret();
                    System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y - tb.Font.Height);
                    int newCharIndex = tb.GetCharIndexFromPosition(newPos);
                    if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
                    {
                        tb.SelectionStart = newCharIndex;
                    }
                    else
                    {
                        tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 1);
                        System.Windows.Forms.SendKeys.Send("{END}");
                    }
                }
            }
            else
            {
                if (oldLineNo > 1)
                {
                    tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 2);
                    tb.ScrollToCaret();
                    System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y - tb.Font.Height);
                    int newCharIndex = tb.GetCharIndexFromPosition(newPos);
                    if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
                    {
                        tb.SelectionStart = newCharIndex;
                    }
                    else
                    {
                        tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 2);
                        System.Windows.Forms.SendKeys.Send("{END}");
                    }
                }
            }
        }
    }

    public static void LineDown(TextBox tb)
    {
        int oldCharIndex = tb.SelectionStart;
        int oldLineNo = tb.GetLineFromCharIndex(oldCharIndex);
        System.Drawing.Point oldCharPos = tb.GetPositionFromCharIndex(oldCharIndex);
        System.Drawing.Point oldCaretPos;
        if (GetCaretPos(out oldCaretPos))
        {
            if (oldCharPos == oldCaretPos)
            {
                if (oldLineNo < tb.GetLineFromCharIndex(tb.Text.Length - 1))
                {
                    tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo + 1);
                    tb.ScrollToCaret();
                    System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y + tb.Font.Height);
                    int newCharIndex = tb.GetCharIndexFromPosition(newPos);
                    if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
                    {
                        tb.SelectionStart = newCharIndex;
                    }
                    else
                    {
                        tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo + 1);
                        System.Windows.Forms.SendKeys.Send("{END}");
                    }
                }
            }
            else
            {
                System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y + tb.Font.Height);
                int newCharIndex = tb.GetCharIndexFromPosition(newPos);
                if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
                {
                    tb.SelectionStart = newCharIndex;
                }
                else
                {
                    tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo);
                    System.Windows.Forms.SendKeys.Send("{END}");
                }
            }
        }
    }
}

Credit for the idea goes to this answer, and you may also want to take a look at MSDN reference on GetCaretPos and other Caret functions.

like image 28
Dil Avatar answered Nov 15 '22 00:11

Dil