C# 多行WPF文本框中的键盘插入符号导航
我有一个使用多行文本框作为数据模板的列表视图 默认情况下,在多行文本框中,启用上下箭头导航。如果您的文本框有两行,则插入符号位于第一行,您按下向下箭头,它会将插入符号置于第二行相同的相对位置 我还在ListView中的文本框之间添加了光标导航。如果您位于文本框的第一行并按向上箭头,则会将焦点设置为ListView中的上一个文本框。类似地,如果您在最后一行并按下,它将转到下一个文本框。但是因为这必须手动完成,所以我必须编写自己的逻辑来维护相对位置。但它很复杂,也有一些问题C# 多行WPF文本框中的键盘插入符号导航,c#,wpf,C#,Wpf,我有一个使用多行文本框作为数据模板的列表视图 默认情况下,在多行文本框中,启用上下箭头导航。如果您的文本框有两行,则插入符号位于第一行,您按下向下箭头,它会将插入符号置于第二行相同的相对位置 我还在ListView中的文本框之间添加了光标导航。如果您位于文本框的第一行并按向上箭头,则会将焦点设置为ListView中的上一个文本框。类似地,如果您在最后一行并按下,它将转到下一个文本框。但是因为这必须手动完成,所以我必须编写自己的逻辑来维护相对位置。但它很复杂,也有一些问题 private void
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("\r\n"))
{
var caretIndex = GetTextBoxCaretIndex();
listView.SelectedIndex--;
var lastLineRegex = new Regex("(.*)(\r\n.*$)", 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("\r\n"))
{
var caretIndex = GetTextBoxCaretIndex();
var lastLineRegex = new Regex("(.*)(\r\n.*$)", 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();
}));
}
private void TextBox\u PreviewKeyDown(对象发送方,KeyEventArgs e)
{
var tb=(发送方作为文本框);
var textBeforeCursor=tb.Text.Substring(0,tb.SelectionStart);
var Text aftercursor=tb.Text.Substring(tb.SelectionStart);
如果(e.Key==Key.Up&!textforecursor.Contains(“\r\n”))
{
var caretIndex=GetTextBoxCaretIndex();
listView.SelectedIndex--;
var lastLineRegex=new Regex((.*)(\r\n.*)”,RegexOptions.Singleline);
var previousString=listView.SelectedItem作为字符串;
var lines=lastLineRegex.Match(previousString);
变量偏移量=行。组[1]。长度;
FocusTextBox(caretIndex+偏移量+2);
}
如果(e.Key==Key.Down&!textAfterCursor.Contains(“\r\n”))
{
var caretIndex=GetTextBoxCaretIndex();
var lastLineRegex=new Regex((.*)(\r\n.*)”,RegexOptions.Singleline);
var previousString=listView.SelectedItem作为字符串;
var lines=lastLineRegex.Match(previousString);
变量偏移量=行。组[1]。长度;
listView.SelectedIndex++;
WriteLine($“CaretIndex:{CaretIndex},Offset:{Offset}”);
FocusTextBox(caretIndex-偏移量-2);
}
}
private int GetTextBoxCaretIndex()
{
var item=listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem)作为ListViewItem;
var textBox=GetVisualChildOfType(项目);
返回textBox.CaretIndex;
}
专用void FocusTextBox(int-caretIndex=0)
{
var item=listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem)作为ListViewItem;
var textBox=GetVisualChildOfType(项目);
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,新操作(()=>
{
textBox.CaretIndex=Math.Min(CaretIndex,textBox.Text.Length);
textBox.SelectionStart=textBox.CaretIndex;
textBox.Focus();
}));
}
这种逻辑可以正常工作,但在某些情况下会中断行之间的默认插入符号导航
插入符号位于顶部文本框的底部,超过8个字符。我按下,它进入第二个文本框,插入符号在第一行,超过8个字符;预期的行为
然后我再次按下,它进入第二行,但在第一个字符,而不是第八个字符。我的代码在这种情况下不执行,所以默认逻辑出现了一些异常情况
我甚至不知道从哪里开始。通过测试,似乎文本框在每行上都有一些关于插入符号位置的内部状态,但是通过查看文本框文档,我没有看到任何与此相关的属性
您可以查看简化的示例项目和演示该问题的完整代码
任何有关默认插入符号导航工作方式的帮助或信息都会有所帮助。谢谢你的时间。我能看出你的沮丧。当我试着这样做时,我发现插入符号有一些奇怪的行为。当您编辑插入符号位置时,默认行为似乎被删除(为什么会转到行的开头)。所以我假设你必须控制插入符号的位置。因此,我在您的
Key\u Up
和Key\u Down
检查中添加了else
子句。我重复了您的逻辑,但使插入符号位置在文本框中显式控制
//...if (e.Key == Key.Up && !textBeforeCursor.Contains("\r\n")){...}
else if (e.Key == Key.Up)
{
var caretIndex = GetTextBoxCaretIndex();
var lastLineRegex = new Regex("(.*)(\r\n.*$)", 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("\r\n")){...}
else if(e.Key == Key.Down)
{
var caretIndex = GetTextBoxCaretIndex();
var lastLineRegex = new Regex("(.*)(\r\n.*$)", 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
检查 解决方案最终是在所有情况下手动控制光标,但需要单独的逻辑。其思想是,获取相对于当前行开头的插入符号位置,并将其新位置设置为下一行的第一个字符加上相对位置,以说明下一行是否小于当前行
if (e.Key == Key.Up)
{
if (!textBeforeCursor.Contains("\r\n"))
{
var caretIndex = GetTextBoxCaretIndex();
listView.SelectedIndex--;
var lastLineRegex = new Regex("(.*)(\r\n.*$)", 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("\r\n") ? 2 : 0;
textBox.CaretIndex = Math.Min(
lineStartIndex + positionOnCurrentLine,
lineStartIndex + textBox.GetLineLength(nextLineIndex) - modifier);
}));
}
}
if(e.Key==Key.Up)
{
如果(!textBeforeCursor.Contains(“\r\n”))
{
var caretIndex=GetTextBoxCaretIndex();
listView.SelectedIndex--;
var lastLineRegex=new Regex((.*)(\r\n.*)”,RegexOptions.Singleline);
var previousString=listView.SelectedItem作为字符串;
var lines=lastLineRegex.Match(previousString);
变量偏移量=行。组[1]。长度;
FocusTextBox(caretIndex+偏移量+2);
}
其他的
{
var item=listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem)作为ListViewItem;
var textBox=GetVisualChildOfType(项目);
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,新操作(()=>
{
var modifier=textBox.GetLineText(nextLineIndex)。包含(“\r\n”)?2:0;
文本框.插入符号