65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 2.0 中 RichTextBox 行号显示

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (38投票s)

2005年11月3日

3分钟阅读

viewsIcon

292472

downloadIcon

12244

标准的 RichTextBox 不允许显示行号。这个用户控件可以实现。

引言

在文本编辑器中显示行号是一个常见的功能。但是 .NET 2.0 中的标准 RichTextBox 不支持此功能。而且在互联网上很难找到合适的解决方案。尤其是,一个不直接使用 Win32 函数的解决方案。

RichTextBox 不是 Windows Forms 中的标准控件。它没有像应该的那样使用 OnPaint 方法和其他类似函数,并且还隐藏了一些正确定制所需的重要属性。克服这个问题的一种方法是使用 win32 API 函数并重写 WndProc 函数。我不喜欢这种做法,但我不得不一次又一次地使用它。我认为这是 .NET 开发人员的错。

幸运的是,对于 RichTextBox 的行号显示,有一个令人满意的解决方案,它不使用纯 Win32 API 函数。

实现

我们将带有行号的 RichTextBox 实现为一个 UserControl。我们不会重写 RichTextBox 中的任何内容,只会使用它的事件。我们的 UserControl 名为 NumberedTextBoxUC,由 SplitContainerLabel (numberLabel) 和 RichTextBox 组成。Label 用于显示行号,RichTextBox 用于显示文本内容,两者都包含在 SplitterContainer 中。

numberLabel 的内容在 RichTextBox 的事件处理程序中更新。这些事件是

  • OnTextChanged
  • OnVScroll
  • OnSizeChanged
  • OnFontChanged

问题

此实现存在几个问题。第一个是滚动。与 VS 源代码编辑器或 TextBox 控件不同,RichTextBox 使用平滑滚动,因此使用滚动条滚动文本以像素为单位,而不是以行为单位。您会注意到第一行显示了一半。这并不总是需要的行为,我希望可以关闭它。另一个问题是大型 Label 的重绘速度,您不能在每个 OnTextChanged 事件处理程序中将太多行打印到 Label。另一个奇怪的问题是 .NET 2.0 中的 RichTextBox 使用奇怪的行缩进,无法关闭或设置为零。当控件顶部对齐时,LabelRichTextBox 中的相同字体会导致不同的行位置。

解决方案

我只显示可见行的行号,因此不会打印不必要的隐藏行号。更新函数称为 updateNumberLabel()。它使用 RichTextBox 函数 GetCharIndexFromPositionGetLineFromCharIndex 来确定第一个和最后一个可见行号。

private void updateNumberLabel()
{
    //we get index of first visible char and 
    //number of first visible line
    Point pos = new Point(0, 0);
    int firstIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int firstLine = richTextBox1.GetLineFromCharIndex(firstIndex);

    //now we get index of last visible char 
    //and number of last visible line
    pos.X = ClientRectangle.Width;
    pos.Y = ClientRectangle.Height;
    int lastIndex = richTextBox1.GetCharIndexFromPosition(pos);
    int lastLine = richTextBox1.GetLineFromCharIndex(lastIndex);

    //this is point position of last visible char, we'll 
    //use its Y value for calculating numberLabel size
    pos = richTextBox1.GetPositionFromCharIndex(lastIndex);

    //finally, renumber label
    numberLabel.Text = "";
    for (int i = firstLine; i <= lastLine + 1; i++)
    {
        numberLabel.Text += i + 1 + "\n";
    }

}

对于不同的行缩进,我找到了一个常数,它最适合字体大小 8。Label 字体的大小对于这个常数来说更大。我希望在未来的 .NET 版本中,这个问题能够得到修复,并且两种字体将完全相同,就像在 .NET 1.1 中一样。

public NumberedTextBoxUC()
{
    InitializeComponent();

    numberLabel.Font = new Font(richTextBox1.Font.FontFamily, 
                              richTextBox1.Font.Size + 1.019f);
}

平滑滚动是另一个导致很多麻烦的问题。我在每个 OnVScroll 事件处理程序中使用小的 numberLabel 位置更新。Label 移动的像素数与 RichTextBox 字体高度的倍数不同,因此是字体高度的模数文本位置。在我看来,如果不使用 Win32 函数,反向解决方案(将文本位置更新为行对齐)是不可能的。以这种方式使用 RichTextBox 函数更新文本位置会导致文本抖动。

private void richTextBox1_VScroll(object sender, EventArgs e)
{
    //move location of numberLabel for amount 
    //of pixels caused by scrollbar
    int d = richTextBox1.GetPositionFromCharIndex(0).Y % 
                              (richTextBox1.Font.Height + 1);
    numberLabel.Location = new Point(0, d);

    updateNumberLabel();
}

结论

我希望这个用户控件能帮助开发人员处理 RichTextBox。我感谢您的建议和对此控件的改进。

© . All rights reserved.