The observer effect “refers to changes that the act of observing will make on the phenomenon being observed.”. How infuriating would be a bug that demonstrates that effect – things work when you see them, don’t when you don’t?
There’s this third party Winforms control I use that does a lot of custom painting. Think of it as a chart control that displays live stock data one stock at a time, with the stock changing every few minutes. Everything was ok, until I happened to see the chart all mixed up on a colleague’s machine. On taking a closer look, I figured out that the images for the previous stock were not cleared by the control, before it drew them for the current stock.
Easy fix – it must be either incorrect data or the control failing to refresh, I thought. I checked the data, that was fine. Explicitly calling Refresh – nope, that didn’t work either.
Hmm, I figured if I could reproduce this on my machine, I could attach a debugger and fix this. And that’s where the fun started. It simply would not happen on my machine – no matter what I tried. Finally, out of desperation, I moved to a different screen that would hide the control as it was painting and sure enough, the painting was messed up when the control was visible again.
It was time to look at the source code for the control. The reason for the garbled painting was easy to find – the control caches the bitmap used for drawing. As newer data comes in, it draws them on top of the cached bitmap. For some reason, the cached bitmap was not getting invalidated when the stock changed. The invalidation of the cached bitmap is controlled by a flag. The flag is set when new data is coming in and is reset when the code hosting the control calls Reset.
My code is missing the Reset, I thought, but that should lead to garbled displays always, not just when the control is invisible. And then it struck me – the code doing the actual invalidation of the cached bitmap was inside the OnPaint method.
If the control happens to be visible when Reset is called, the painting code gets triggered, the flag is false and the bitmap is disposed. If it is invisible during Reset AND becomes visible only after new data starts flowing in, the flag would have been reset to true and the control would continue drawing on top of the cached bitmap, messing up the display.
The fix was to invalidate the cache inside Reset as well.