WinForms TextBox滚动至不可见行时无法获取正确当前行问题排查
WinForms TextBox滚动后无法获取正确当前行的问题分析与解决
问题描述
使用WinForms的TextBox控件时,当滚动到不可见的行,无法获取正确的当前行;但将TextBox展开显示所有文本行时,功能正常。
怀疑的问题代码片段
// current line text int char_index = UserTextTextBox.GetCharIndexFromPosition(location); int line_index = UserTextTextBox.GetLineFromCharIndex(char_index); if ((line_index >= 0) && (line_index < UserTextTextBox.Lines.Length)) { m_current_text = UserTextTextBox.Lines[line_index].ToString(); } else { // all text m_current_text = UserTextTextBox.Text; }
完整代码
private bool m_currenttext = false; private bool m_usertext_mode = false; private int m_usertext_selection_start = 0; private int m_usertext_selection_length = 0; private Point m_caret_position = new Point(0,0); private bool m_mouse_down = false; private void UserTextTextBox_MouseDown(object sender, MouseEventArgs e) { m_mouse_down = true; } private void UserTextTextBox_MouseMove(object sender, MouseEventArgs e) { if (m_mouse_down) { if (UserTextTextBox.SelectionLength != m_usertext_selection_length) { m_usertext_selection_length = UserTextTextBox.SelectionLength; CalculateUserTextValue(e.Location); } } } private void UserTextTextBox_MouseUp(object sender, MouseEventArgs e) { m_mouse_down = false; CalculateUserTextValue(e.Location); // backup m_caret_position int char_index = UserTextTextBox.GetFirstCharIndexOfCurrentLine(); if (char_index >= 0) { //BUG in GetPositionFromCharIndex when index is final it returns Point(0, 0) m_caret_position = UserTextTextBox.GetPositionFromCharIndex(char_index); if ((char_index > 0) && (m_caret_position == new Point(0, 0))) { // last line so calculate value of all text CalculateUserTextValue(); } else { CalculateUserTextValue(m_caret_position); } } } private void UserTextTextBox_Enter(object sender, EventArgs e) { this.AcceptButton = UserTextValueButton; m_usertext_mode = true; m_usertext_mode_values_sequence = false; CalculateUserTextValue(m_caret_position); } private void UserTextTextBox_Leave(object sender, EventArgs e) { m_usertext_mode = false; } private void UserTextTextBox_TextChanged(object sender, EventArgs e) { //do nothing } private void UserTextTextBox_KeyDown(object sender, KeyEventArgs e) { if (ModifierKeys == Keys.Control) { if (e.KeyCode == Keys.A) { UserTextTextBox.SelectAll(); e.SuppressKeyPress = true; // suppress annoying beep due to parent not having an AcceptButton } else if (e.KeyCode == Keys.F) { // Find dialog } else if (e.KeyCode == Keys.H) { // Replace dialog } else if (e.KeyCode == Keys.S) { UserTextSaveLabel_Click(m_usertext_load_control, null); e.SuppressKeyPress = true; // suppress annoying beep due to parent not having an AcceptButton } else { // don't e.SuppressKeyPress = true; } } } private void UserTextTextBox_KeyUp(object sender, KeyEventArgs e) { if (ModifierKeys == Keys.Control) { if (e.KeyCode == Keys.A) { CalculateUserTextValue(new Point(0, 0)); } } else { int char_index = UserTextTextBox.GetFirstCharIndexOfCurrentLine(); if (char_index >= 0) { //??? BUG in GetPositionFromCharIndex when index is final it returns Point(0, 0) m_caret_position = UserTextTextBox.GetPositionFromCharIndex(char_index); if ((char_index > 0) && (m_caret_position == new Point(0, 0))) { // last line so calculate value of all text CalculateUserTextValue(); } else { CalculateUserTextValue(m_caret_position); } } } e.SuppressKeyPress = true; // suppress annoying beep due to parent not having an AcceptButton } private void CalculateUserTextValue(Point location) { m_usertext_selection_length = UserTextTextBox.SelectionLength; m_usertext_selection_start = UserTextTextBox.SelectionStart; if (UserTextTextBox.SelectionLength > 0) { // selected text m_current_text = UserTextTextBox.SelectedText; } else { // current line text int char_index = UserTextTextBox.GetCharIndexFromPosition(location); int line_index = UserTextTextBox.GetLineFromCharIndex(char_index); if ((line_index >= 0) && (line_index < UserTextTextBox.Lines.Length)) { m_current_text = UserTextTextBox.Lines[line_index].ToString(); } else { // all text m_current_text = UserTextTextBox.Text; } } m_current_text = m_current_text.Replace("\r", ""); m_current_text = m_current_text.Replace("\t", ""); m_current_text = m_current_text.Replace("_", ""); m_current_text = m_current_text.Replace("\n ", "\n"); m_current_text = m_current_text.Replace(" \n", "\n"); while (m_current_text.Contains(" ")) { m_current_text = m_current_text.Replace(" ", " "); } m_current_text = m_current_text.Trim(); DisplayValuesSequence(m_current_text); }
问题原因
问题出在GetCharIndexFromPosition(location)方法上:WinForms的TextBox对于滚动出可视区域的行,该方法无法正确将鼠标/光标位置映射到对应的字符索引。因为该方法依赖控件的可视渲染结果,未渲染的行无法提供准确的位置计算,并非控件Bug,而是设计局限。
解决方案
直接基于文本的逻辑索引获取当前行,不依赖可视位置参数。
1. 修改核心逻辑
替换CalculateUserTextValue方法中获取当前行的代码块:
else { // 直接获取光标所在行的起始索引,不受滚动状态影响 int currentLineStartIndex = UserTextTextBox.GetFirstCharIndexOfCurrentLine(); if (currentLineStartIndex >= 0) { int line_index = UserTextTextBox.GetLineFromCharIndex(currentLineStartIndex); if ((line_index >= 0) && (line_index < UserTextTextBox.Lines.Length)) { m_current_text = UserTextTextBox.Lines[line_index].ToString(); } else { m_current_text = UserTextTextBox.Text; } } else { m_current_text = UserTextTextBox.Text; } }
2. 优化事件调用
在MouseUp、KeyUp、Enter等事件中,无需再通过GetPositionFromCharIndex获取位置传入方法,直接调用无参版本的CalculateUserTextValue(可新增如下方法):
private void CalculateUserTextValue() { m_usertext_selection_length = UserTextTextBox.SelectionLength; m_usertext_selection_start = UserTextTextBox.SelectionStart; if (UserTextTextBox.SelectionLength > 0) { m_current_text = UserTextTextBox.SelectedText; } else { int currentLineStartIndex = UserTextTextBox.GetFirstCharIndexOfCurrentLine(); if (currentLineStartIndex >= 0) { int line_index = UserTextTextBox.GetLineFromCharIndex(currentLineStartIndex); if ((line_index >= 0) && (line_index < UserTextTextBox.Lines.Length)) { m_current_text = UserTextTextBox.Lines[line_index].ToString(); } else { m_current_text = UserTextTextBox.Text; } } else { m_current_text = UserTextTextBox.Text; } } // 后续文本处理逻辑不变 m_current_text = m_current_text.Replace("\r", ""); m_current_text = m_current_text.Replace("\t", ""); m_current_text = m_current_text.Replace("_", ""); m_current_text = m_current_text.Replace("\n ", "\n"); m_current_text = m_current_text.Replace(" \n", "\n"); while (m_current_text.Contains(" ")) { m_current_text = m_current_text.Replace(" ", " "); } m_current_text = m_current_text.Trim(); DisplayValuesSequence(m_current_text); }
之后修改事件中的调用,比如MouseUp里改成CalculateUserTextValue(),KeyUp同理,避免传入不可靠的Point参数。
内容的提问来源于stack exchange,提问作者Ali Adams
相关产品推荐
相关产品推荐

