【问题标题】:Detecting forced line split in RichTextBox检测 RichTextBox 中的强制行拆分
【发布时间】:2020-04-30 11:12:05
【问题描述】:

我有一个应用程序以管道格式 (HL7) 分析数据消息,为此,它有一个与 RichTextBox 同步的 DataGridView。具体来说,当你点击DataGridView中的某个属性时,会跳转到RichTextBox中的对应位置,反之亦然。

RichTextBox 禁用了自动换行,因此我可以轻松地将编辑器中的行与实际数据中的行匹配。

但是,我目前必须处理在某些部分包含二进制文件的 Base64 转储的消息,而且大内容使富文本框无论如何都会换行。这使计算变得混乱,并且当匹配实际消息文本中的返回位置时,我得到错误的数据,分析失败,并且通常,当实际的下一行短于单击的位置时,我得到一个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);
}

正确的行为:

点击后,函数将返回点[28, 4]。

强制换行的错误行为:

在这次点击中,函数将返回点 [6,5],它实际上应该是 [2813,4]。这会导致它显示下一行的分析,并且如前所述,如果点击是在该行中超出下一个分析行末尾的位置上,则会导致ArgumentOutOfRangeException

有什么办法可以弥补这种强制线分割?我需要能够准确地确定实际文本中的位置才能进行分析。

请注意,分行似乎不可预测;我不知道它尝试拆分的最大长度是多少,或者它决定拆分的字符是可能的。

另请注意,两个称为RichTextBox 的函数,即GetLineFromCharIndexGetFirstCharIndexFromLine,正确对应于屏幕上实际显示的内容......但屏幕上显示的内容是真实数据的不正确表示。事实上,它甚至不对应于RichTextBox 自己的.Lines 属性的输出,它为我提供了纯文本行数组中的内容。

我宁愿避免使用 .Lines 属性,因为我注意到通常从富文本框中提取文本的函数相当慢。

【问题讨论】:

  • EM_SETWORDBREAKPROC -- How to Use Word and Line Break Information(请参阅警告,即使此处不适用)。如果委托函数未正确实现(在自定义控件中),您将获得 random 异常。对于委托指针,可以使用Marshal.GetFunctionPointerForDelegate
  • 我怀疑一旦文本超过底层 RichEdit 控件的formatting rectangle,它就会强制换行。也许您可以颠倒您的逻辑并使用 DGV 作为导航源并显示 3 个堆叠的 RTB(当前 DGV 行之前的行、当前行、下一行)?
  • IIRC,如果代理总是返回 0,那么你在任何地方都没有换行符。
  • @TnTinMn 导航实际上是双向的:在文本中点击跳转到datagrid中的属性分析,在datagrid中点击跳转到文本中所选属性的开头。由于该工具的主要目的是轻松识别管道数据,因此文本侧导航绝对至关重要。
  • 比这差得多。 RichEdit 控件不提供该信息(TextBox 控件也不提供),它只是索引字符位置(换行符在内部处理,它也不是公共信息,您需要实现自己的换行例程来处理这些信息,使用EditWordBreakDelegate,如前所述),因此每次您请求 Lines[] 属性时,this is what happens。如果您的文本很长,这会阻止您使用该属性。

标签: c# winforms richtextbox


【解决方案1】:

如果您通过发送EM_SETTYPOGRAPHYOPTIONS message 启用richedit 控件的高级排版选项,在RichTextBox.WordWrap 属性设置为false 的情况下,不会发生长文本行的强制换行。

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 的自我回答的评论中提到,使用“文本对象模型”的解决方案是可能的。在对该技术进行更彻底的测试后,我发现它仅对强制换行文本的第一行是准确的,之后它在确定行位置时包括了之前的换行行。因此,我没有展示这种方法。

【讨论】:

  • 哇哦,这简直比满屏 Base64 的屏幕好看多了。谢谢!
  • 这很好用,很高兴找到它。肯定有您知道用户不想或不需要滚动的用例,例如 xml 文件中的 base64 ......我猜 Nyerguds 和我有同样的情况,.NET 不适应!
【解决方案2】:

在我寻找解决方案的过程中,我发现,正如我所担心的那样,RichTextBox.Lines 不是一个简单的指向数组的无害指针,而是一个将富文本框内容转换为纯文本并使用它的复杂操作在这种情况下会严重影响性能。

但是,这让我意识到,我在整个项目中的 很多 代码已经 访问该属性以进行随机操作,尤其是在任何换行。

我决定为该行数组创建一个缓存变量,该变量在RichTextBoxTextChanged 事件中被清除。所有在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 在这样的击键后在分析中被多次调用,所以总体上结果仍然得到了极大的优化。

有了这个系统,在我的小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);
    }

【讨论】:

  • 我正准备使用Text Object Model 为您提供解决方案,但意外发生了。我使用的库允许在 RTB 上启用ADVANCEDTYPOGRAPHY;启用该选项后,不会发生软换行符。如果您有兴趣,我可以发布该替代方案。
  • 哦,拜托了!正如我所说,这种缓存显然是非常需要的,但我很想根本不让线路中断。它会让编辑器看起来更干净。
  • @TnTinMn 你的意思是like this(最后一段 - 是的,我知道,这与全文对齐有关)?根据文本结构,可能无法保证不会发生 unexpected 换行符。但它可能适用。
  • @jimi,我的意思是使用EM_SETTYPOGRAPHYOPTIONS 消息设置TO_ADVANCEDTYPOGRAPHY。所以我想答案是肯定的。我将不得不检查有关您的 cmets 的 RTB 源,因为某种原因禁用了排版。
  • @TnTinMn 别担心。它没有被禁用,因为它不起作用(或者它是 dangerous :)。这是因为它没有在混合语言环境中进行测试(也不是真正为混合语言环境设计的),所以当你同时拥有 LTR 和 RTL(或者实际上只是 RTL)活动语言时,你可能会得到非常奇怪的结果。跨度>
猜你喜欢
  • 2013-01-07
  • 2012-05-12
  • 1970-01-01
  • 1970-01-01
  • 2019-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多