0
votes

I've got a subclassed text box that acquires keyboard focus (caret) only if the user intentionally clicks into it (it's part of a complex control where the user may not always want such behavior).

Accordingly, I have to give the text box this focus in code (with Keyboard.Focus), so I cannot rely on existing behavior to do it.

The problem is that when the user clicks in (and I invoke Keyboard.Focus), the caret position is not set. This is not intuitive, and mildly frustrating, necessitating a second click action from the user (once keyboard focus is acquired) to place the caret in the correct spot they wanted.

I would use GetCharacterIndexFromPoint, but it gives you the wrong index! If your mouse is beyond the last character, it will place the caret before it when clicked, which is arguably more irritating than no behavior at all in my opinion.

To this end, how can I take the mouse position and derive a suitable caret location from it, in a manner that mimics the out of the box behavior of the control (caret jumps to nearest valid position to mouse)?

I've researched this previously and came up blank.

3
Please show us how you're using GetCharacterIndexFromPoint.user743414
MyTextBox.CaretIndex = GetCharacterIndexFromPoint(Mouse.GetPosition(this), true);TernaryTopiary

3 Answers

0
votes

I've found an answer.

It's solution 2 here by user EriBeh, copied below:

        SizeF size;
        using (Graphics graphics = Grapics.FromImage(new Bitmap(1, 1)))
        {
            size = graphics.MeasureString(CustomTextBox.Text, new Font(CustomTextBox.FontFamily.ToString(),     Convert.ToSingle(CustomTextBox.FontSize), System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel));
        }

        if (mousePoint.X >= size.Width) return CustomPasswordBox.Password.Length;
        else CustomTextBox.GetCharacterIndexFromPoint(mousePoint, true);

The solution is not quite right, however, as it does not account for any padding or text alignment. Merely calculating the width of the text is insufficient, one must also consider where the text sits in the control and derive its right-most point (relative to the control) accordingly, and then can test whether the mouse is further right or not against that derived value.

Accordingly, I've adapted this thusly (my text will always be centered in the control):

        SizeF size;
        using (var graphics = Graphics.FromImage(new Bitmap(1, 1)))
        {
            size = graphics.MeasureString(Text, new Font(FontFamily.ToString(), Convert.ToSingle(FontSize), System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel));
        }
        var mousepos = Mouse.GetPosition(this);
        var textXLocationAtRightmostEdge = (ActualWidth / 2) + (size.Width / 2);
        CaretIndex = mousepos.X >= textXLocationAtRightmostEdge ? Text.Length : GetCharacterIndexFromPoint(mousepos, true);

Superior solutions appreciated.

0
votes

I came to similar issue, when my textbox also can wrap, so WIDTH of text or textbox cant be used. So i figured out (and didnt found any issue with it yet) this solution

MouseDownStartPosition = e.GetPosition(textBox);

//check if user clicked some exact character
var characterIndexFalse = textBox.GetCharacterIndexFromPoint(MouseDownStartPosition, false);
//get nearest character to mouse
var characterIndex = textBox.GetCharacterIndexFromPoint(MouseDownStartPosition, true);

//GetCharacterIndexFromPoint often returns last but one character position when clicked right after textbox
//so check if characterIndexFalse is -1 (text not clicked) and if nearest character is last but one, assume that 
//user clicked behind string
if ((characterIndexFalse == -1) && (characterIndex + 1 == textBox.Text.Length)) {
   characterIndex++;
}

Brief explanation:

GetCharacterIndexFromPoint(true) returns nearest character which has issue with last character in string and selecting last but one.

GetCharacterIndexFromPoint(false) returns exact clicked character or -1 if no character clicked

So to fix this case, i check if no exact character is clicked, and nearest character is last but one i know that caret needs to be placed after last character.

0
votes

Can I add my 2 cents here?

Why not have the WPF system take care of this by itself?

        // Get the protected OnMouseDown method via reflection.
        var textBoxBaseType = typeof(TextBoxBase);
        var onMouseDownMethod = textBoxBaseType.GetMethod("OnMouseDown", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        System.Diagnostics.Debug.Assert(onMouseDownMethod != null);

        Point mousePoint = Mouse.GetPosition(txtBox);
        var mea = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left);
        mea.RoutedEvent = e.RoutedEvent;    // Where e = the EventArgs parameter passed on to this method
        txtBox.SelectionLength = 0;
        onMouseDownMethod.Invoke(txtBox, new[] { mea });