的WinForms RichTextBox的:如何重新格式化异步,兵不血刃TextChanged事件

人气:1,038 发布:2022-09-10 标签: .net multithreading winforms richtextbox manualresetevent

问题描述

这是一个随访,以 WinForms RichTextBox的:如何在框TextChanged进行格式化

This is a followup to WinForms RichTextBox: how to perform a formatting on TextChanged?

我有一个WinForms应用程序有一个RichTextBox,应用程序自动精彩的内容表示框。因为格式化可能需要较长时间的大文件,10秒或更长,我已经设置了一个BackgroundWorker做了重新格式化一个RichTextBox的。 它遍历文本,并进行了一系列的这些:

I have a Winforms app with a RichTextBox, the app auto-highlights the content of said box. Because the formatting can take a long time for a large document, 10 seconds or more, I've set up a BackgroundWorker to do the re-formatting of a RichTextBox. It walks through the text and performs a series of these:

rtb.Select(start, length);
rtb.SelectionColor = color;

虽然是这样做的,在UI保持响应。

While it is doing this, the UI remains responsive.

BackgroundWorker的是从TextChanged事件拉开序幕。像这样的:

The BackgroundWorker is kicked off from the TextChanged event. like this:

private ManualResetEvent wantFormat = new ManualResetEvent(false);
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
    xpathDoc = null;
    nav = null;
    _lastChangeInText = System.DateTime.Now;
    if (this.richTextBox1.Text.Length == 0) return;
    wantFormat.Set();
}

在后台工作的方法是这样的:

The background worker method looks like this:

private void DoBackgroundColorizing(object sender, DoWorkEventArgs e)
{
    do
    {
        wantFormat.WaitOne();
        wantFormat.Reset();

        while (moreToRead())
        {
            rtb.Invoke(new Action<int,int,Color>(this.SetTextColor,
                      new object[] { start, length, color} ) ;
        }                

    } while (true);
}

private void SetTextColor(int start, int length, System.Drawing.Color color)
{
   rtb.Select(start, length);
   rtb.SelectionColor= color;
}

不过,每次分配selectionColor设置使TextChanged事件火灾:一个死循环。

But, every assignment to SelectionColor causes the TextChanged event to fire: An endless loop.

如何区分文本的变化,从文字的变化,从BackgroundWorker的起源做了格式化来自外部的?

我也可以解决这个问题,如果我能独立文本格式的变化检测文本内容的变化。

I could also solve this if I could detect a text content change independently of a text format change.

推荐答案

我采取的方法是运行在一个BackgroundWorker格式化逻辑。我选择了这个,因为格式将采取长时间,超过1两秒钟,所以我不能在UI线程上做到这一点。

The approach I took was to run the formatter logic in a BackgroundWorker. I chose this because the format would take a "long" time, more than 1 second or two, so I couldn't do it on the UI thread.

只是重申这个问题:每个电话的BackgroundWorker的做出对RichTextBox.SelectionColor二传手再次打响了TextChanged事件,这将从头再来的BG线程。在TextChanged事件,我能找到没有办法区分用户键入的东西事件从程序格式化文本事件。所以,你可以看到它是变化的无限发展。

Just to restate the problem: every call made by the BackgroundWorker to the setter on RichTextBox.SelectionColor fired the TextChanged event again, which would start the BG thread all over again. Within the TextChanged event, I could find no way to distinguish a "user has typed something" event from a "program has formatted the text" event. So you can see it would be an infinite progression of changes.

简单的方法不起作用

一个常用的方法(as由Eric )建议是禁用的文字改变事件处理的文本更改处理程序中运行时。但当然,这不会对我的情况下工作,因为正在通过的背景的线程生成的文本改变(selectionColor设置的变化)。他们没有被文字处理程序变化的范围之内进行。因此,简单的方法来过滤用户发起的事件不会对我的情况,其中一个后台线程正在修改工作。

A common approach (as suggested by Eric) is to "disable" text change event handling while running within the text change handler. But of course this won't work for my case, because the text changes (SelectionColor changes) are being generated by a background thread. They are not being performed within the scope of a text change handler. So the simple approach to filtering user-initiated events will not work for my case, where a background thread is making changes.

其他尝试检测用户发起的更改

我试着用RichTextBox.Text.Length,以此来区分我的格式化线程源自用户所做的更改在RichTextBox中的变化,在RichTextBox。如果长度没有改变,我的理由,那么变化是一个格式更改我的code做的,而不是用户编辑。但检索RichTextBox.Text属性是昂贵的,并且这样做,对于每TextChange事件作出整个UI太慢。即使这是足够快,它不会在一般的情况下工作,因为用户作出的格式改变,也。而且,用户的编辑可能产生相同长度的文本,如果它是一个typeover排序操作。

I tried using the RichTextBox.Text.Length as a way to distinguish the changes in the richtextbox originating from my formatter thread from the changes in the richtextbox made by the user. If the Length had not changed, I reasoned, then the change was a format change done by my code, and not a user edit. But retrieving the RichTextBox.Text property is expensive, and doing that for every TextChange event made the entire UI unacceptably slow. Even if this was fast enough, it doesn't work in the general case, because users make format changes, too. And, a user edit might produce the same length text, if it was a typeover sort of operation.

我希望捕获和处理的TextChange事件仅检测变化从用户发起。因为我不能这样做,我改变了应用程序使用密钥preSS事件和粘贴事件。因此,我现在不明白虚假TextChange事件,由于格式更改(如RichTextBox.SelectionColor = Color.Blue)。

I was hoping to catch and handle the TextChange event ONLY to detect changes originating from the user. Since I couldn't do that, I changed the app to use the KeyPress event and the Paste event. As a result I now don't get spurious TextChange events due to formatting changes (like RichTextBox.SelectionColor = Color.Blue).

信令的工作线程来完成它的工作

OK,我有一个线程在运行,可以做的格式更改。从概念上讲,它这样做:

OK, I've got a thread running that can do formatting changes. Conceptually, it does this:

while (forever)
    wait for the signal to start formatting
    for each line in the richtextbox 
        format it
    next
next

我怎么能告诉BG线程开始格式化?

How can I tell the BG thread to start formatting?

我用了一个 ManualResetEvent的。当检测到密钥preSS的,关键preSS处理器设置该事件(打开它)。后台工作人员正在等待同样的事件。当它被接通时,对BG螺纹将其关闭,并开始格式化。

I used a ManualResetEvent. When a KeyPress is detected, the keypress handler sets that event (turns it ON). The background worker is waiting on the same event. When it is turned on, the BG thread turns it off, and begins formatting.

但是,如果BG工人的已的格式?在这种情况下,一个新的密钥preSS可能已经改变的文本框的内容,和迄今所做的任何格式现在可能是无效的,所以该格式必须重新启动。我真正想要的格式线程是这样的:

But what if the BG worker is already formatting? In that case, a new keypress may have changed the content of the textbox, and any formatting done so far may now be invalid, so the formatting must be restarted. What I really want for the formatter thread is something like this:

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的。如果格式化过程中发生的另一个重要preSS事件,那么该事件再次进入一个信号状态。当格式化看到,它的重新激活时,格式化捞出,并开始从文的开头再次格式化,像西西弗斯。更智能的机制将重新启动在发生变化在文档中的点格式化。

With this logic, when the ManualResetEvent is set (turned on), the formatter thread detects that, and resets it (Turns it off), and begins formatting. It walks through the text and decides how to format it. Periodically the formatter thread checks the ManualResetEvent again. If another keypress event occurs during formatting, then the event again goes to a signalled state. When the formatter sees that it's re-signalled, the formatter bails out and starts formatting again from the beginning of the text, like Sisyphus. A more intelligent mechanism would restart formatting from the point in the document where the change occurred.

延迟性格式

另一种扭曲:我不想格式化开始其格式工作的马上的每关键preSS。作为人的类型,击键之间的正常暂停小于600-700ms。如果格式化开始没有延迟的格式,那么它会尝试开始按键之间的格式。 pretty的毫无意义的。

Another twist: I don't want the formatter to begin its formatting work immediately with every KeyPress. As a human types, the normal pause between keystrokes is less than 600-700ms. If the formatter starts formatting without a delay, then it will try to begin formatting between keystrokes. Pretty pointless.

所以格式化逻辑,只有开始做了格式化的工作,如果它检测到的时间超过600毫秒的击键暂停。接收到信号后,它等待600毫秒,并且如果没有出现过居间键presses,则分型已停止和格式应该开始。如果出现了中间的变化,然后格式化什么都不做,得出结论认为,用户仍然打字。在code:

So the formatter logic only begins to do its formatting work if it detects a pause in keystrokes of longer than 600ms. After receiving the signal, it waits 600ms, and if there have been no intervening keypresses, then the typing has stopped and the formatting should start. If there has been an intervening change, then the formatter does nothing, concluding that the user is still typing. In code:

private System.Threading.ManualResetEvent wantFormat = new System.Threading.ManualResetEvent(false);

关键preSS事件:

The keypress event:

private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    _lastRtbKeyPress = System.DateTime.Now;
    wantFormat.Set();
}

在colorizer方法,它运行在后台线程:

In the colorizer method, which runs in the background thread:

....
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;

用户体验是任何格式,而他们正在打字时。一旦输入暂停,开始格式化。如果打字重新开始,格式化停止,并再次等待。

The user experience is that no formatting occurs while they are typing. Once typing pauses, formatting starts. If typing begins again, the formatting stops and waits again.

在格式更改禁用滚动

有一个最后的问题:格式化一个RichTextBox的文字需要调用的 RichTextBox.Select()的,这会导致的 RichTextBox的自动滚动选定的文本,当RichTextBox中具有焦点。由于格式是发生在同一时间的用户主要集中在控制,读,也许编辑文本,我需要一种方法来燮preSS滚动。我找不到使用RTB的公共接口的方式,以prevent滚动,虽然我确实发现很多人在intertubes问一下吧。一些试验后,我发现,使用Win32 的SendMessage()调用(从user32.dll中),发送 WM_SETREDRAW 前和选择后, (),可以prevent滚动在RichTextBox中调用选择时,()。

There was one final problem: formatting the text in a RichTextBox requires a call to RichTextBox.Select(), which causes the RichTextBox to automatically scroll to the text selected, when the RichTextBox has focus. Because the formatting is happening at the same time the user is focused in the control, reading and maybe editing the text, I needed a way to suppress the scrolling. I could not find a way to prevent scrolling using the public interface of RTB, although I did find many people in the intertubes asking about it. After some experimenting, I found that using the Win32 SendMessage() call (from user32.dll), sending WM_SETREDRAW before and after the Select(), can prevent the scroll in the RichTextBox when calling Select().

由于我是诉诸的PInvoke至prevent滚动,我也用在SendMessage函数的PInvoke获取或设置在文本框中选择或插入符号(的 EM_GETSEL 或的 EM_SETSEL ),并设置上的选择格式(的 EM_SETCHARFORMAT )。该PInvoke的方式结束了比使用管理界面稍快。

Because I was resorting to pinvoke to prevent the scrolling, I also used pinvoke on SendMessage to get or set the selection or caret in the textbox (EM_GETSEL or EM_SETSEL), and to set the formatting on the selection (EM_SETCHARFORMAT). The pinvoke approach ended up being slightly faster than using the managed interface.

批更新为响应

而因为preventing滚动发生的一些计算开销,我决定分批到文档所做的更改。相反,突出一个连续的部分或词,逻辑保留的亮点或格式更改列表进行。几乎每隔一段时间,它适用于在同一时间到文件也许30的变化。然后它清除该列表,并回到分析和排队哪些需要作出格式的变化。它的速度不够快输入中的文档应用更改这些批处理时,不会中断。

And because preventing scrolling incurred some compute overhead, I decided to batch up the changes made to the document. Instead of highlighting one contiguous section or word, the logic keeps a list of highlight or format changes to make. Every so often, it applies maybe 30 changes at a time to the document. Then it clears the list and goes back to analyzing and queuing which format changes need to be made. It's fast enough that typing in the doc is not interrupted when applying these batches of changes.

其结果是文件被自动格式化,彩色离散块,当没有输入时发生的事情。如果用户密钥presses之间有足够的时间的推移,整个文档最终将被格式化。这是在200毫秒为1K的XML文档,也许2秒为一个30K的文档,或10秒的10万文档。如果用户编辑文档,那么这是正在进行的任何格式被中止,并格式化全部重新开始。

The upshot is the document gets auto-formatted and colorized in discrete chunks, when no typing is happening. If enough time passes between user keypresses, the entire document will eventually get formatted. This is under 200ms for a 1k XML doc, maybe 2s for a 30k doc, or 10s for a 100k doc. If the user edits the document, then any formatting that was in progress is aborted, and the formatting starts all over again.

唷!

我很惊讶,因为一些看似简单的格式化一个RichTextBox,而在它的用户类型是如此的复杂。但我不能想出什么简单的未锁定的文本框,但避免了怪异的滚动行为。

I am amazed that something as seemingly simple as formatting a richtextbox while the user types in it is so involved. But I couldn't come up with anything simpler that did not lock the text box, yet avoided the weird scrolling behavior.

您可以查看code 了解东西我上面描述。

You can view the code for the thing I described above.

281