HtmlPanel flashing on click or selection drag (IsSelectionEnabled = true)

May 30, 2014 at 4:32 AM
Hey there,

After migrating to the latest version, I found another issue in which OnPaint triggers another OnPaint and causes flashing when IsSelelectionEnable is true (as well as repeating message loop).

The flashing is only noticeable when composited drawing and double-buffering are enabled, but ideally you don't want this kind of redundant messaging going on. In my case, it leads to flashing that makes the control unusable.

However, you can easily see this yourself even with the Demo. Bring up Perfmon or Task Manager > Performance tab and monitor CPU cores. Click in the Demo in HtmlPanel and hold mouse down. Watch the CPU usage! You will burn one full core as the application spins away pumping WM_PAINTS which result in more WM_PAINT's. There is a endless message loop going on for as long as you hold the mouse down, even if you don't move it one pixel.

The code which introduced this behavior is the following which was added to OnPaint:
            // call mouse move to handle paint after scroll or html change affecting mouse cursor.
            var mp = PointToClient(MousePosition);
            _htmlContainer.HandleMouseMove(this, new MouseEventArgs(MouseButtons.None, 0, mp.X, mp.Y, 0));
The HandleMouseMove is the culprt. When IsSelectionEnabled is true, it will result in another full control invalidation and repaint.

This code did not exist in 1.4.1. To work around the issue, I have simply commented it out, as the issue it addressed was far less critical than the bug it introduces. Perhaps there is a way this can be modified so that both goals can be achieved.

I believe the issue is when the clicks (or holds mouse down, drag-selects, etc) on the control you get the following callstack:
  • OnMouseDown in HtmlPanel
  • HandleMouseDown in HtmlContainer
  • HandleMouseDown in SelectionHandler
  • parent.Invalidate in SelectionHandler
  • OnPaint in HtmlPanel
  • HandleMouseMove in HtmlContainer
  • HandleMouseMove in SelectionHandler
  • HandleSelection in SelectionHandler
  • control.Invalidate (which results in another full invalidation and repaint)
I believe the core issue is that inner control.Invalidate should not be called in this callstack. When HandleMouseMove is being called as part of OnMouseMove, then it should be. But when it is being called as part of the OnPaint, it shouldn't be, right? Otherwise, every time you click in the control, it immediately causes two full Invalidate's, the second of which is triggered from within the OnPaint of the first. This is what leads to the message pump chain that persists until user lifts the mouse.

My theory is that since you do not have window compositing or double-buffering enabled you aren't noticing the extra repaints because ethey don't need to actually happen. But they are there, and the CPU cycles are being burnt. However, when compositing and double-buffering are enabled, the repaints have to actually happen, and the result is unusable flashing.

Not sure the best way to fix this, you are closer to the code an I am, but as I said for now I've just commented out this new code in HtmlPanel.OnPaint.

What I think is needed is a different version of HandleMouseMove, or some flag that controls it's behavior so that the inner repaint doesn't happen in this case.

Hopefully this helps and make sense. ;-)

May 30, 2014 at 8:50 AM
Awesome details!
Will find a fix.
Many thanks.