I had a custom control, pretty much completely self-drawn, that was dynamically getting added to a Form in the dropdown mechanism in PropertyGrid
.
If I read your problem right, it's basically the same overall issue.
I had BackColor set and was fine. But with DoubleBuffer set, it seems to just ignore it for a bit.
Taking in all of the existing comments on the question, I was able to have this solution, and hope the details help someone else make their own.
Problem 1
My control flickered unless I repainted whole control every OnPaint
.
If did proper painting only attempting to paint things that intersected the e.ClipRectangle
, then it would flicker in real-time, like effects from invalidates that had to do when the mouse was moved. Attempt to Paint whole thing, no problem.
I watched trace output real-time and watched all of my draws and invalidates print, and never a time where should be introducing flicker myself, on myself directly.
Problem 2
If I turn on DoubleBuffered
, then instead it flickered badly as the control was shown every time opening the dropdown. From white background only for 100-200 ms, at least, then to black and rendered foreground suddenly in one step.
Problem 3
I never actually needed double buffer. Both the problem 1 and 2 were always related to WM_ERASEBKGND
.
The actual original flicker seems to be caused by WM_ERASEBKGND
very briefly visibly whacking my already painted thing, right before I painted it again. Did not really need actual double buffering in my case. For some reason when I was blindly painting the whole list maybe the timing was different and was painting over the erase before could see it.
All that said, if I turn DoubleBuffered
on which removes WM_ERASEBKGND
via turning on AllPaintingInWmPaint
, then initial background won't be painted until I suppose the double buffer and paint process works its way, all the way through the first time.
Problem 4
If I let the WM_ERASEBKGND
"just happen", then it's still double painting, and I don't know if or when it might end up flicking anyway for someone else.
If I only turn on SetStyle(OptimizedDoubleBuffer
, then I now know I'll be letting the initial background paint and not flicker on show. But I also know I'm using double buffer to mask the WM_ERASEBKGND
for the entirety of the life of the control after it is shown.
So....
I did something like this:
Part 1
if the user of the control sees a need to double buffer doing something that might flicker, create a way for them to easily enable it, without forcing AllPaintingInWmPaint
. Like if they want to use Paint or a DrawXXX event and doing something that animates or something related to mouse movement.
bool _isDoubleBuffer;
[Category("Behavior")]
public virtual bool DoubleBuffer
{
get { return _isDoubleBuffer; } // dont care about real SetStyle state
set
{
if (value != DoubleBuffer)
{
_isDoubleBuffer = value;
SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
}
}
}
Part 2
Manage WM_ERASEBKGND
yourself, as the choice is otherwise 1) always off with AllPaintingInWmPaint, and no background paint on show, or 2) violating what double buffer expects where it would be always masking the WM_ERASEBKGND.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
if (_hasPaintForeground && _isDoubleBuffer)
return;
}
base.WndProc(ref m);
}
Part 3
You are now your own decider of what AllPaintingInWmPaint means.
In this case would want the initial messages to process like normal. When we knew for sure the .Net and DoubleBuffer side was finally kicking it, by seeing our first real paint happen, then turn WM_ERASEBKGND off for the duration.
bool _hasPaintForeground;
protected override void OnPaint(PaintEventArgs e)
{
// your paint code here, if any
base.OnPaint(e);
if (!_hasPaintForeground) // read cheaper than write every time
{
_hasPaintForeground = true;
}
}
Part 4
In my case, I also had originally gimped the OnBackground draw, which works if you are opaque drawing each element yourself in OnPaint. This allowed me to not have double buffer on for so long until I started following the clip and changed the timing so that I started also seeing the other WM_ERASEBKGND side effects and issues.
protected override void OnPaintBackground(PaintEventArgs e)
{ // Paint default background until first time paint foreground.
if (!_hasPaintForeground) // This will kill background image but stop background flicker
{ // and eliminate a lot of code, and need for double buffer.
base.OnPaintBackground(e);
} // PaintBackground is needed on first show so no white background
}
I may not need this part anymore, but if my DoubleBuffer
is off then I would. So long as I'm always painting opaque in OnPaint covering the whole draw Clip area.
Addendum:
In addition to all of that....
Separate issue with text render.
It looks like if I render only 250 x 42, like two rows and two text renders, which all occur in one OnPaint, verified with Diagnostics.Trace.WriteLine
, then the text renders at least one monitor frame later, every single time. Making it look like the text is flashing. Is just 2x paint background single color then 2x paint text each for rows.
However, if I attempt to paint the whole client area of like 250 x 512 or whatever, like 17 rows, even though the e.Clip is exactly those two rows, because I'm the one that invalidated it, then no flicker of the text, 0 flicker.
There is either some timing issue or other side effect. But that's 17 chances instead of two, for at least one row to flicker text where the whole background is shown before the text renders, and it never happens. If I try to only render rows that are in the clip area it happens every time.
There is def something going with .Net or Windows. I tried with both g.DrawString
and TextRenderer.DrawText
and they both do it. Flicker if draw 2, not flicker if attempt to draw 17. Every time.
- Maybe has something to do with drawing text near the mouse pointer, when
OnPaint
comes back too quickly?
- Maybe if I draw enough things or OnPaint takes longer to come back, it's doing double buffer anyway? Dunno
So....
It's a good thing I went through this exercise with the original question.
I may choose to just render the whole client every time, but I'll never be able to do it the "right way" without something like my example code above.