检测 RichTextBox 中的强制分行
Detecting forced line split in RichTextBox
我有一个分析管道格式 (HL7) 数据消息的应用程序,为此,它有一个与 RichTextBox
同步的 DataGridView
。具体来说,当你点击DataGridView
中的一个属性时,它会跳转到RichTextBox
中的相应位置,反之亦然。
RichTextBox
禁用自动换行,因此我可以轻松地将编辑器中的行与实际数据中的行匹配。
但是,我目前必须处理其中某些部分包含二进制文件的 Base64 转储的消息,并且大内容使得富文本框无论如何都会换行。这使计算变得混乱,当匹配实际消息文本中的 returned 位置时,我得到错误的数据,分析失败,并且通常,当实际的下一行时,我得到一个 ArgumentOutOfRangeException
比该行上的点击位置短。
这是代码:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
Int32 currentLine = this.rtxtMessage.GetLineFromCharIndex(selectionStart);
Int32 currentPos = selectionStart - this.rtxtMessage.GetFirstCharIndexFromLine(currentLine);
return new Point(currentPos, currentLine);
}
正确的行为:
单击此函数时,函数将 return 指向 [28, 4]。
强制换行的错误行为:
单击此函数时,函数将 return 指向 [6,5],而实际上它应该是 [2813,4]。这会导致它显示对下一行的分析,并且如前所述,如果单击该行中超出下一个分析行末尾的位置,则会导致 ArgumentOutOfRangeException
.
有什么办法可以弥补这种强制分线吗?我需要能够准确判断在实际文本中的位置来做分析。
请注意,分割线似乎无法预测;我不知道它尝试拆分后的最大长度是多少,或者它决定拆分的字符是可能的。
另请注意,两个称为 RichTextBox
的函数,即 GetLineFromCharIndex
和 GetFirstCharIndexFromLine
,正确对应于屏幕上实际显示的内容...但屏幕上显示的是真实数据的错误表示。事实上,它甚至不对应 RichTextBox
自己的 .Lines
属性 的输出,它以纯文本行数组的形式给出了内容。
虽然我宁愿避免使用 .Lines
属性,因为我注意到一般来说,从富文本框中提取文本的功能相当慢。
在我寻找解决方案的过程中,我发现,正如我担心的那样,RichTextBox.Lines
并不是一个简单的无害数组指针,而是一个将富文本框内容转换为纯文本的复杂操作, 在这种情况下使用它会严重影响性能。
然而,这让我意识到在整个项目中我的代码 很多 已经 访问 属性随机操作,尤其是在任何行更改时发生的分析部分。
我决定为那个行数组创建一个缓存变量,它在 RichTextBox
的 TextChanged
事件中被清除。所有在 RichTextBox
上获取行的实例都被调用这个小函数所取代:
private String[] GetTextboxLines()
{
if (this.m_LineCache != null)
return this.m_LineCache;
String[] lines = this.rtxtMessage.Lines;
this.m_LineCache = lines;
return lines;
}
当简单地在富文本编辑器中输入文本时,这仍然相当繁重,因为任何击键基本上都会清除数组,然后分析器操作会再次获取它,但由于该工具首先是分析器,而且仅其次是编辑,这不是一个大问题。即便如此,RichTextBox.Lines
在这样一个击键后的分析中被多次调用,所以总体而言,结果仍然 得到了极大的优化。
有了这个系统,lines 数组的使用在我的小 GetCursorPosition()
函数中又变得可行了,所以我也调整它以利用新的缓存值:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
String[] lines = this.GetTextboxLines();
Int32 nrOfLines = lines.Length;
Int32 y;
for (y = 0; y < nrOfLines; y++)
{
Int32 lineLen = lines[y].Length;
// Can be equal if at the very end of a line.
if (selectionStart <= lineLen)
return new Point(selectionStart, y);
// +1 to compensate for the line break character,
// which is only one byte in a rich text box.
selectionStart -= (lineLen + 1);
}
return new Point(0, nrOfLines - 1);
}
看来,如果您通过发送 EM_SETTYPOGRAPHYOPTIONS message 启用 richedit 控件的高级排版选项,则长文本行的强制换行不会发生 RichTextBox.WordWrap
属性 设置为假的。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
richTextBox1.HandleCreated += RTBHandledCreated;
FillRTB();
}
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
private void RTBHandledCreated(object sender, EventArgs e)
{
const Int32 WM_USER = 0x400;
const Int32 EM_SETTYPOGRAPHYOPTIONS = WM_USER + 202;
const Int32 EM_GETTYPOGRAPHYOPTIONS = WM_USER + 203;
const Int32 TO_ADVANCEDTYPOGRAPHY = 1;
const Int32 TO_SIMPLELINEBREAK = 2;
SendMessage(richTextBox1.Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
}
private void FillRTB()
{
for (Int32 i = 0; i <= 3; i++)
{
richTextBox1.AppendText($"Line {i}: ");
if (i == 1 || i == 3 )
{
StringBuilder sb = new StringBuilder(100000);
for (Int32 j = 0; j < sb.Capacity; j += 10)
{
for (Int32 k = 0; k <= 9; k++)
{
sb.Append(k.ToString());
}
}
richTextBox1.AppendText(sb.ToString());
}
if (i != 3)
{
richTextBox1.AppendText($"{Environment.NewLine}");
}
}
richTextBox1.SelectionStart = 0;
}
private void richTextBox1_SelectionChanged(object sender, EventArgs e)
{
label1.Text = richTextBox1.GetLineFromCharIndex(richTextBox1.SelectionStart).ToString();
}
}
请注意,我最初在对 OP 的自我回答的评论中提到,使用 "Text Object Model" 的解决方案是可能的。在对这项技术进行更彻底的测试时,我发现它只对第一行强制换行文本是准确的,之后它在确定行位置时包括了之前的换行。因此,我没有展示该方法。
我有一个分析管道格式 (HL7) 数据消息的应用程序,为此,它有一个与 RichTextBox
同步的 DataGridView
。具体来说,当你点击DataGridView
中的一个属性时,它会跳转到RichTextBox
中的相应位置,反之亦然。
RichTextBox
禁用自动换行,因此我可以轻松地将编辑器中的行与实际数据中的行匹配。
但是,我目前必须处理其中某些部分包含二进制文件的 Base64 转储的消息,并且大内容使得富文本框无论如何都会换行。这使计算变得混乱,当匹配实际消息文本中的 returned 位置时,我得到错误的数据,分析失败,并且通常,当实际的下一行时,我得到一个 ArgumentOutOfRangeException
比该行上的点击位置短。
这是代码:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
Int32 currentLine = this.rtxtMessage.GetLineFromCharIndex(selectionStart);
Int32 currentPos = selectionStart - this.rtxtMessage.GetFirstCharIndexFromLine(currentLine);
return new Point(currentPos, currentLine);
}
正确的行为:
单击此函数时,函数将 return 指向 [28, 4]。
强制换行的错误行为:
单击此函数时,函数将 return 指向 [6,5],而实际上它应该是 [2813,4]。这会导致它显示对下一行的分析,并且如前所述,如果单击该行中超出下一个分析行末尾的位置,则会导致 ArgumentOutOfRangeException
.
有什么办法可以弥补这种强制分线吗?我需要能够准确判断在实际文本中的位置来做分析。
请注意,分割线似乎无法预测;我不知道它尝试拆分后的最大长度是多少,或者它决定拆分的字符是可能的。
另请注意,两个称为 RichTextBox
的函数,即 GetLineFromCharIndex
和 GetFirstCharIndexFromLine
,正确对应于屏幕上实际显示的内容...但屏幕上显示的是真实数据的错误表示。事实上,它甚至不对应 RichTextBox
自己的 .Lines
属性 的输出,它以纯文本行数组的形式给出了内容。
虽然我宁愿避免使用 .Lines
属性,因为我注意到一般来说,从富文本框中提取文本的功能相当慢。
在我寻找解决方案的过程中,我发现,正如我担心的那样,RichTextBox.Lines
并不是一个简单的无害数组指针,而是一个将富文本框内容转换为纯文本的复杂操作, 在这种情况下使用它会严重影响性能。
然而,这让我意识到在整个项目中我的代码 很多 已经 访问 属性随机操作,尤其是在任何行更改时发生的分析部分。
我决定为那个行数组创建一个缓存变量,它在 RichTextBox
的 TextChanged
事件中被清除。所有在 RichTextBox
上获取行的实例都被调用这个小函数所取代:
private String[] GetTextboxLines()
{
if (this.m_LineCache != null)
return this.m_LineCache;
String[] lines = this.rtxtMessage.Lines;
this.m_LineCache = lines;
return lines;
}
当简单地在富文本编辑器中输入文本时,这仍然相当繁重,因为任何击键基本上都会清除数组,然后分析器操作会再次获取它,但由于该工具首先是分析器,而且仅其次是编辑,这不是一个大问题。即便如此,RichTextBox.Lines
在这样一个击键后的分析中被多次调用,所以总体而言,结果仍然 得到了极大的优化。
有了这个系统,lines 数组的使用在我的小 GetCursorPosition()
函数中又变得可行了,所以我也调整它以利用新的缓存值:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
String[] lines = this.GetTextboxLines();
Int32 nrOfLines = lines.Length;
Int32 y;
for (y = 0; y < nrOfLines; y++)
{
Int32 lineLen = lines[y].Length;
// Can be equal if at the very end of a line.
if (selectionStart <= lineLen)
return new Point(selectionStart, y);
// +1 to compensate for the line break character,
// which is only one byte in a rich text box.
selectionStart -= (lineLen + 1);
}
return new Point(0, nrOfLines - 1);
}
看来,如果您通过发送 EM_SETTYPOGRAPHYOPTIONS message 启用 richedit 控件的高级排版选项,则长文本行的强制换行不会发生 RichTextBox.WordWrap
属性 设置为假的。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
richTextBox1.HandleCreated += RTBHandledCreated;
FillRTB();
}
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
private void RTBHandledCreated(object sender, EventArgs e)
{
const Int32 WM_USER = 0x400;
const Int32 EM_SETTYPOGRAPHYOPTIONS = WM_USER + 202;
const Int32 EM_GETTYPOGRAPHYOPTIONS = WM_USER + 203;
const Int32 TO_ADVANCEDTYPOGRAPHY = 1;
const Int32 TO_SIMPLELINEBREAK = 2;
SendMessage(richTextBox1.Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
}
private void FillRTB()
{
for (Int32 i = 0; i <= 3; i++)
{
richTextBox1.AppendText($"Line {i}: ");
if (i == 1 || i == 3 )
{
StringBuilder sb = new StringBuilder(100000);
for (Int32 j = 0; j < sb.Capacity; j += 10)
{
for (Int32 k = 0; k <= 9; k++)
{
sb.Append(k.ToString());
}
}
richTextBox1.AppendText(sb.ToString());
}
if (i != 3)
{
richTextBox1.AppendText($"{Environment.NewLine}");
}
}
richTextBox1.SelectionStart = 0;
}
private void richTextBox1_SelectionChanged(object sender, EventArgs e)
{
label1.Text = richTextBox1.GetLineFromCharIndex(richTextBox1.SelectionStart).ToString();
}
}
请注意,我最初在对 OP 的自我回答的评论中提到,使用 "Text Object Model" 的解决方案是可能的。在对这项技术进行更彻底的测试时,我发现它只对第一行强制换行文本是准确的,之后它在确定行位置时包括了之前的换行。因此,我没有展示该方法。