You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 01:47:04