Обнаружение принудительного разделения строк в RichTextBox - PullRequest
0 голосов
/ 30 апреля 2020

У меня есть приложение, которое анализирует сообщения данных в трубопроводном формате (HL7), и для этого у него есть DataGridView, которое синхронизируется с RichTextBox. В частности, когда вы нажимаете на свойство в 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);
}

Правильное поведение: Correct

При этом щелчке функция вернет точку [28, 4].

Неверное поведение в строке с принудительным переносом: Incorrect

При этом щелчке функция вернет точку [6,5], где она должна быть на самом деле [2813,4]. Это заставляет его показывать анализ для следующей строки, и, как уже упоминалось, если щелчок находится в месте в строке, которая находится за концом следующей анализируемой строки, это вызывает ArgumentOutOfRangeException.

Is Есть ли способ, чтобы компенсировать это принудительное разделение линии? Мне нужно иметь возможность точно определить положение в реальном тексте, чтобы выполнить анализ.

Обратите внимание, что разделение линий не кажется предсказуемым; Я не знаю, какова максимальная длина, после которой он пытается разделить, или символы, по которым он решает, возможно разделение.

Также обратите внимание, две названные RichTextBox функции, а именно GetLineFromCharIndex и GetFirstCharIndexFromLine, правильно соответствуют тому, что фактически отображается на экране ... но то, что показано на экране, является неправильным представлением реальных данных. Фактически, он даже не соответствует выводу собственного свойства RichTextBox *1034*, которое дает мне содержимое в массиве строк с простым текстом.

Я бы лучше не использовал это свойство .Lines, хотя, как я заметил, в общем, функции для извлечения текста из поля расширенного текста довольно медленные.

Ответы [ 2 ]

1 голос
/ 30 апреля 2020

Похоже, что если вы включите параметр расширенной типографии элемента управления richedit, отправив сообщение EM_SETTYPOGRAPHYOPTIONS , то принудительное перенос длинных текстовых строк не происходит, если для свойства 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();
    }
}

Обратите внимание, что я первоначально упомянул в комментарии к самоответу ОП, что решение с использованием "Текстовой объектной модели" возможно. При более тщательном тестировании этого метода я обнаружил, что он был точным только для первой строки текста с принудительной переносом, после чего он включал предыдущие строки с переносом в определении позиции строки. Поэтому я не показываю этот метод.

0 голосов
/ 30 апреля 2020

В своем поиске решений я обнаружил, что, как я и опасался, RichTextBox.Lines - это не простой безобидный указатель на массив, а на самом деле сложная операция по преобразованию содержимого текстового поля в простой текст и его использованию. в этой ситуации это может серьезно повлиять на производительность.

Однако это заставило меня понять, что партия моего кода в рамках проекта уже обращалась к этому свойству для случайных операций, особенно в части анализа, которая происходит при любом изменении строки.

Я решил создать переменную кэша для этого массива строк, которая очищается в событии TextChanged RichTextBox. Все экземпляры, которые извлекли строки в 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);
    }
...