programmatically move caret in textbox, line up an

2019-05-17 06:45发布

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.

3条回答
叛逆
2楼-- · 2019-05-17 06:46
    /// ------------------------------------------------------------------------------------
    /// <summary>
    /// Processes up key when a grid cell is in the edit mode. This overrides the default
    /// behavior in a grid cell when it's being edited so using the up arrow will move the
    /// IP up one line rather than moving to the previous row.
    /// </summary>
    /// ------------------------------------------------------------------------------------
    protected virtual bool ProcessUpKey(TextBox txtBox)
    {
        // Don't override the default behavior if all the text is selected or not multi-line.
        if (txtBox.SelectedText == txtBox.Text || !txtBox.Multiline)
            return false;

        int selectionPosition = txtBox.SelectionStart;
        // Getting the position after the very last character doesn't work.
        if (selectionPosition == txtBox.Text.Length && selectionPosition > 0)
            selectionPosition--;
        Point pt = txtBox.GetPositionFromCharIndex(selectionPosition);

        if (pt.Y == 0)
            return false;

        pt.Y -= TextRenderer.MeasureText("x", txtBox.Font).Height;
        txtBox.SelectionStart = txtBox.GetCharIndexFromPosition(pt);
        return true;
    }

    /// ------------------------------------------------------------------------------------
    /// <summary>
    /// Processes down key when a grid cell is in the edit mode. This overrides the default
    /// behavior in a grid cell when it's being edited so using the down arrow will move the
    /// IP down one line rather than moving to the next row.
    /// </summary>
    /// ------------------------------------------------------------------------------------
    protected virtual bool ProcessDownKey(TextBox txtBox)
    {
        // Don't override the default behavior if all the text is selected or not multi-line.
        if (txtBox.SelectedText == txtBox.Text || !txtBox.Multiline)
            return false;

        int chrIndex = txtBox.SelectionStart;
        Point pt = txtBox.GetPositionFromCharIndex(chrIndex);
        pt.Y += TextRenderer.MeasureText("x", txtBox.Font).Height;
        var proposedNewSelection = txtBox.GetCharIndexFromPosition(pt);
        if (proposedNewSelection <= chrIndex)
            return false; // Don't let "down" take you *up*.
        txtBox.SelectionStart = proposedNewSelection;
        return true;
    }
查看更多
Lonely孤独者°
3楼-- · 2019-05-17 06:50

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.

查看更多
可以哭但决不认输i
4楼-- · 2019-05-17 07:07

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.

查看更多
登录 后发表回答