Multiline WPF文本框中的键盘插入导航



我有一个使用多行文本框作为其datatemplate的listView。

默认情况下,在多行文本框中,启用了箭头导航。如果您的文本框有两行,则套在第一行,然后按向下箭头,它将Caret放在第二行的相对位置上。

我还在ListView中的文本框之间添加了光标导航。如果您处于文本框的第一行,然后按UP Arrow,则将焦点设置为ListView中的上一个文本框。同样,如果您处于最后一行并按下,则将转到下一个文本框。但是,由于必须手动完成,因此我必须编写自己的逻辑来维持相对位置。但这很复杂,有一些问题。

private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    var tb = (sender as TextBox);
    var textBeforeCursor = tb.Text.Substring(0, tb.SelectionStart);
    var textAfterCursor = tb.Text.Substring(tb.SelectionStart);
    if (e.Key == Key.Up && !textBeforeCursor.Contains("rn"))
    {
        var caretIndex = GetTextBoxCaretIndex();
        listView.SelectedIndex--;
        var lastLineRegex = new Regex("(.*)(rn.*$)", RegexOptions.Singleline);
        var previousString = listView.SelectedItem as string;
        var lines = lastLineRegex.Match(previousString);
        var offset = lines.Groups[1].Length;
        FocusTextBox(caretIndex + offset + 2);
    }
    if (e.Key == Key.Down && !textAfterCursor.Contains("rn"))
    {
        var caretIndex = GetTextBoxCaretIndex();
        var lastLineRegex = new Regex("(.*)(rn.*$)", RegexOptions.Singleline);
        var previousString = listView.SelectedItem as string;
        var lines = lastLineRegex.Match(previousString);
        var offset = lines.Groups[1].Length;
        listView.SelectedIndex++;
        Console.WriteLine($"CaretIndex: {caretIndex}, Offset: {offset}");
        FocusTextBox(caretIndex - offset - 2);
    }
}
private int GetTextBoxCaretIndex()
{
    var item = listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) as ListViewItem;
    var textBox = GetVisualChildOfType<TextBox>(item);
    return textBox.CaretIndex;
}
private void FocusTextBox(int caretIndex = 0)
{
    var item = listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) as ListViewItem;
    var textBox = GetVisualChildOfType<TextBox>(item);
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
    {
        textBox.CaretIndex = Math.Min(caretIndex, textBox.Text.Length);
        textBox.SelectionStart = textBox.CaretIndex;
        textBox.Focus();
    }));
}

这种逻辑的作品,但在某些情况下会破坏行之间的默认插入式导航。

这是一个示例案例的.gif

套在顶部文本框的底线上,有8个字符。我按下,它转到第二个文本框,其中列在第一行,8个字符覆盖;预期行为。

然后,我再次按下,然后登上第二行,但在第一个角色而不是第8个角色。在这种情况下,我的代码没有执行,因此默认逻辑正在发生不寻常的事情。

我什至不确定从哪里开始。通过测试它似乎就像文本框在每行上有一些内部状态,但是通过查看文本框文档,我看不到任何属性。

您可以在GitHub上查看一个划板的示例项目和完整的代码。

有关默认CARET导航如何工作的任何帮助或信息将有所帮助。感谢您的时间。

解决方案最终是在所有情况下手动控制光标,但需要单独的逻辑。这个想法是,获得相对于当前行的开始的角度,并将其新位置设置为下一行的第一个字符以及相对位置,如果下一行小于当前行。

if (e.Key == Key.Up)
{
    if (!textBeforeCursor.Contains("rn"))
    {
        var caretIndex = GetTextBoxCaretIndex();
        listView.SelectedIndex--;
        var lastLineRegex = new Regex("(.*)(rn.*$)", RegexOptions.Singleline);
        var previousString = listView.SelectedItem as string;
        var lines = lastLineRegex.Match(previousString);
        var offset = lines.Groups[1].Length;
        FocusTextBox(caretIndex + offset + 2);
    }
    else
    {
        var item = listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) as ListViewItem;
        var textBox = GetVisualChildOfType<TextBox>(item);
        var currentLineIndex = textBox.GetLineIndexFromCharacterIndex(textBox.CaretIndex);
        var positionOnCurrentLine = textBox.CaretIndex - textBox.GetCharacterIndexFromLineIndex(currentLineIndex);
        var nextLineIndex = currentLineIndex - 1;
        var lineStartIndex = textBox.GetCharacterIndexFromLineIndex(nextLineIndex);
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
        {
            var modifier = textBox.GetLineText(nextLineIndex).Contains("rn") ? 2 : 0;
            textBox.CaretIndex = Math.Min(
                lineStartIndex + positionOnCurrentLine,
                lineStartIndex + textBox.GetLineLength(nextLineIndex) - modifier);
        }));
    }
}

逻辑对于向下箭头键是相同的,但是您将NextLineIndex更改为CurrentLineNINDEX 1。

此解决方案不如默认的CARET管理,因为默认管理会说明您是否处于线路的末端(无论长度如何(,并使您保持线路结束,直到您手动更改为止。该解决方案有时还会选择一个稍有意外的位置,因为字符具有不同的宽度。

我尝试了一种使用TextBox.GetRectFromCharacterEndex和textbox.getCharacterIndexFrompoint的解决方案,但它似乎并没有改善功能。也许有更好的方法。

我可以看到你的挫败感。当我尝试这件事时,我发现了Caret的某些奇怪行为。看来,当您编辑CARET位置时,删除了默认行为(为什么进入行的开头(。因此,我假设您必须始终控制着男人的位置。因此,我在您的Key_UpKey_Down检查中添加了else子句。我重复了您的逻辑,但使其成为文本框中明确控制的角色位置。

     //...if (e.Key == Key.Up && !textBeforeCursor.Contains("rn")){...}
     else if (e.Key == Key.Up)
     {
        var caretIndex = GetTextBoxCaretIndex();
        var lastLineRegex = new Regex("(.*)(rn.*$)", RegexOptions.Singleline);
        var previousString = listView.SelectedItem as string;
        var lines = lastLineRegex.Match(previousString);
        var offset = lines.Groups[1].Length;
        FocusTextBox(caretIndex - offset - 2);
     }
    //...if (e.Key == Key.Down && !textAfterCursor.Contains("rn")){...}
    else if(e.Key ==  Key.Down)
    {
        var caretIndex = GetTextBoxCaretIndex();
        var lastLineRegex = new Regex("(.*)(rn.*$)", RegexOptions.Singleline);
        var previousString = listView.SelectedItem as string;
        var lines = lastLineRegex.Match(previousString);
        var offset = lines.Groups[1].Length;
        FocusTextBox(caretIndex + offset + 2);
    }

肯定可以清理代码。我像以前一样离开了它,因为它可以在每个阶段进行调试。因此,如您所见,我将把它保留给您以进行重构。您还必须在previousString上添加null检查。

最新更新