Подход, который я выбрал, состоял в том, чтобы запустить логику форматирования в BackgroundWorker. Я выбрал это, потому что формат займет «долгое» время, больше чем 1 секунду или две, поэтому я не мог сделать это в потоке пользовательского интерфейса.
Просто для повторения проблемы: каждый вызов, сделанный BackgroundWorker установщику в RichTextBox.SelectionColor, снова вызывал событие TextChanged, что снова начало поток BG. В рамках события TextChanged я не смог найти способа отличить событие «пользователь что-то набрал» от события «программа отформатировала текст». Таким образом, вы можете видеть, что это будет бесконечная последовательность изменений.
Простой подход не работает
Обычный подход (, предложенный Эриком ) состоит в том, чтобы «отключить» обработку событий изменения текста во время работы в обработчике изменения текста. Но, конечно, это не будет работать для моего случая, потому что изменения текста (изменения SelectionColor) генерируются потоком background . Они не выполняются в рамках обработчика изменения текста. Так что простой подход к фильтрации пользовательских событий не будет работать для моего случая, когда фоновый поток вносит изменения.
Другие попытки обнаружить изменения, инициированные пользователем
Я попытался использовать RichTextBox.Text.Length как способ отличить изменения в richtextbox, происходящие из моего потока форматирования, от изменений в richtextbox, сделанных пользователем. Если длина не изменилась, я рассуждал, тогда изменение было изменением формата, сделанным моим кодом, а не редактированием пользователя. Но получение свойства RichTextBox.Text стоит дорого, и выполнение этого для каждого события TextChange делало весь пользовательский интерфейс недопустимо медленным. Даже если это было достаточно быстро, это не работает в общем случае, потому что пользователи также вносят изменения в формат. Кроме того, пользовательское редактирование может выдать текст такой же длины, если это будет операция типа typeover.
Я надеялся поймать и обработать событие TextChange ТОЛЬКО для обнаружения изменений, происходящих от пользователя. Так как я не мог этого сделать, я изменил приложение, чтобы использовать событие KeyPress и событие Paste. В результате теперь я не получаю ложных событий TextChange из-за изменений форматирования (например, RichTextBox.SelectionColor = Color.Blue).
Сигнализация рабочего потока для выполнения его работы
ОК, у меня запущен поток, который может вносить изменения в форматирование. Концептуально он делает это:
while (forever)
wait for the signal to start formatting
for each line in the richtextbox
format it
next
next
Как мне сообщить BG-потоку начать форматирование?
Я использовал ManualResetEvent . При обнаружении нажатия клавиши обработчик нажатия клавиши устанавливает это событие (включает его). Фоновый рабочий ожидает того же события. Когда он включен, поток BG выключает его и начинает форматирование.
Но что, если рабочий BG уже форматирует? В этом случае новое нажатие клавиши могло изменить содержимое текстового поля, и любое выполненное до сих пор форматирование теперь может быть недопустимым, поэтому форматирование необходимо перезапустить. То, что я действительно хочу для темы форматирования, выглядит примерно так:
while (forever)
wait for the signal to start formatting
for each line in the richtextbox
format it
check if we should stop and restart formatting
next
next
С этой логикой, когда ManualResetEvent установлен (включен), поток форматирования обнаруживает это, и сбрасывает его (выключает) и начинает форматирование. Он просматривает текст и решает, как его отформатировать. Периодически поток форматирования снова проверяет ManualResetEvent. Если во время форматирования происходит другое нажатие клавиши, то это событие снова переходит в сигнальное состояние. Когда средство форматирования видит, что оно повторно сигнализирует, оно форматируется и снова начинает форматирование с начала текста, как Сизиф. Более интеллектуальный механизм возобновит форматирование с того места в документе, где произошло изменение.
Форматирование с отложенным началом
Еще один поворот: я не хочу, чтобы форматировщик начинал свою работу по форматированию немедленно с каждым нажатием клавиши. Как у людей, нормальная пауза между нажатиями клавиш составляет менее 600-700 мс. Если средство форматирования начинает форматирование без задержки, то оно попытается начать форматирование между нажатиями клавиш. Довольно бессмысленно.
Таким образом, логика форматирования начинает выполнять свою работу по форматированию только в том случае, если обнаруживает паузу при нажатии клавиш длительностью более 600 мс. После получения сигнала он ждет 600 мс, и если не было никаких нажатий клавиш, ввод текста прекращается, и форматирование должно начинаться. Если произошли промежуточные изменения, то средство форматирования ничего не делает, делая вывод, что пользователь все еще печатает. В коде:
private System.Threading.ManualResetEvent wantFormat = new System.Threading.ManualResetEvent(false);
Событие нажатия клавиши:
private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
_lastRtbKeyPress = System.DateTime.Now;
wantFormat.Set();
}
В методе colorizer, который запускается в фоновом потоке:
....
do
{
try
{
wantFormat.WaitOne();
wantFormat.Reset();
// We want a re-format, but let's make sure
// the user is no longer typing...
if (_lastRtbKeyPress != _originDateTime)
{
System.Threading.Thread.Sleep(DELAY_IN_MILLISECONDS);
System.DateTime now = System.DateTime.Now;
var _delta = now - _lastRtbKeyPress;
if (_delta < new System.TimeSpan(0, 0, 0, 0, DELAY_IN_MILLISECONDS))
continue;
}
...analyze document and apply updates...
// during analysis, periodically check for new keypress events:
if (wantFormat.WaitOne(0, false))
break;
Пользовательский опыт заключается в том, что во время набора текста форматирование не происходит. После ввода паузы начинается форматирование. Если набор текста начинается снова, форматирование останавливается и снова ждет.
Отключение прокрутки во время изменения формата
Была одна последняя проблема: форматирование текста в RichTextBox требует вызова RichTextBox.Select () , что заставляет RichTextBox автоматически прокручивать к выбранному тексту, когда RichTextBox имеет фокус. Поскольку форматирование происходит в то же время, когда пользователь сосредоточен на управлении, чтении и, возможно, редактировании текста, мне был необходим способ подавить прокрутку. Я не мог найти способ предотвратить прокрутку, используя открытый интерфейс RTB, хотя я обнаружил, что многие люди в интертубах спрашивают об этом. После некоторых экспериментов я обнаружил, что с помощью вызова Win32 SendMessage () (из user32.dll), отправляющего WM_SETREDRAW до и после Select (), можно предотвратить прокрутку в RichTextBox при вызове Select ().
Поскольку я прибегал к pinvoke для предотвращения прокрутки, я также использовал pinvoke в SendMessage для получения или установки выделения или каретки в текстовом поле ( EM_GETSEL или EM_SETSEL ) и установить форматирование для выделения ( EM_SETCHARFORMAT ). Подход pinvoke оказался немного быстрее, чем при использовании управляемого интерфейса.
Пакетные обновления для отзывчивости
И поскольку предотвращение прокрутки повлекло за собой некоторые накладные расходы, я решил объединить изменения, внесенные в документ. Вместо выделения одного смежного раздела или слова логика хранит список выделенных изменений или изменений формата, которые необходимо внести. Время от времени он применяет к документу 30 изменений одновременно. Затем он очищает список и возвращается к анализу и организации очередей, какие изменения формата необходимо внести. Достаточно быстро, что ввод в документ не прерывается при применении этих пакетов изменений.
В результате документ автоматически форматируется и раскрашивается в отдельные фрагменты, когда печать не происходит. Если между нажатиями клавиш пользователя проходит достаточно времени, весь документ в конечном итоге будет отформатирован. Это меньше 200 мс для документа XML объемом 1 Кб, может быть 2 с для документа 30 Кб или 10 с для документа 100 Кб. Если пользователь редактирует документ, то любое выполняемое форматирование прерывается, и форматирование начинается заново.
Уф!
Я поражен тем, что что-то столь же простое, как форматирование richtextbox, когда пользователь вводит в него текст, очень важно. Но я не мог придумать ничего более простого, которое не блокировало бы текстовое поле, но избегало странной прокрутки.
Вы можете просмотреть код для того, что я описал выше.