I have a panel I draw on, and I'm doing it in the paint event. I want to delete previous changes before drawing, but If I use Invalidate() or Refresh() it is redrawing it forever (it is flickering), and the strange thing is I call Invalidate() before any drawing, so if it's just forcing panel paint I shouldn't see the drawing
Edit: I figured out this:
void _paint(object sender, PaintEventArgs e)
{
Panel P = (Panel)sender;
if (painted = false) { painted = true; P.Invalidate(); }
Shows(30);
painted = false;
}
It stopped flickering. Anyway, turns out I've never needed that, it is repainting the panel even without the Invalidation. But strangely when I hide only part of the panel(either with form resizing or dragging window over the panel),when becoming visible again it is painted, not repainted(I can tell because the anti-aliasing becomes solid color, producing shitty result), that's why I thought I need to Invalidate the panel. Invalidating won't stop it from happening. I think probably another event is triggered, not paint.
I encountered a problem with my control flickering when I called Invalidate with a program I created not too long ago. I found overriding the OnPaintBackGround did the trick, it may work for you too;
protected override void OnPaintBackground(PaintEventArgs e)
{
/* Override to stop the background being repainted -> this stops flashing of the text */
}
First if all, the following condition is lacking an = sign:
if (painted = false) { painted = true; P.Invalidate(); }
It should read
if (painted == false) { painted = true; P.Invalidate(); }
or
if (!painted) { painted = true; P.Invalidate(); }
Secondly, double buffering is in many cases a good idea. To do so, paint all the content into an off-screen bitmap that has the same size as the client area of your control and then draw that bitmap to the target control if needed. Do not draw the off-screen bitmap in the OnPaint event, but whenever the content to be drawn changes!
Example:
private void PaintOffscreen()
{
// Do whatever necessary to draw the offscreen bitmap
...
// Cause the control to repaint itself. Change OnPaint to draw the bitmap only
Invalidate();
}
void _paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(offscreenBitmap, 0, 0);
}
Also, override the OnPaintBackground method as suggested by HorHAY to prevent the control from clearing the background before painting itself.
Related
Scenario
Having a Windows Forms Form-derived form that contains a Panel-derived control:
The form gets a black background color set:
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
And the panel control is configured to be double buffered and the like, as described here, here and here:
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
The actual drawing is done inside these overrides in MyPanel:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
Wrong behaviour
Every now and then, when the form is initially shown, my owner drawn panel is shortly drawn as white, before my own drawing code is actually called:
After my drawing code is called, everything is drawn correctly and I never can reproduce the white background again:
Even resizing the window and panel does not make it flicker in white.
Enforcing the wrong behavior
I can enforce the initial white-drawn of my panel, if I put a sleep in the Shown event handler of my form:
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
The panel is shown in white for 1000 ms just before my owner-drawn paint code is being called.
My question
How can I avoid the initial white displaying when having an owner drawn/custom drawn panel?
My goal is to have the control "know" its initial background color (black in my example) right from the start, not after it is initially shown.
Some thoughts
I've tried to play with all kind of things including the CreateParams property, but with no visible success.
My initial idea was to provide some initial background color through the WNDCLASSEX structure, but after digging through the Reference Source, I still have no clue whether this is possible and would help.
Whole code
Just to be safe, following is my whole code.
MyForm.cs:
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
}
MyPanel.cs:
public class MyPanel : Panel
{
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
}
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.
I am using Visual Studio 2013 to write a Windows Forms C# application. I want to draw game board on Form1_Load and draw pawns on button click. I have written two methods: InitDraw() and Draw(). When both method are in Form1_Load() or button1_Click() it's OK, but if InitDraw() is in Form1_Load() and Draw() is in button1_Click() - it draws only if I press Alt or move windows out of screen and move back to screen. I added Invalidate() but this does not help.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Bitmap drawBitmap;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
InitDraw();
}
private void button1_Click(object sender, EventArgs e)
{
Draw();
}
private void InitDraw()
{
drawBitmap = new Bitmap(500, 500);
pictureBox1.Image = drawBitmap;
}
private void Draw()
{
Graphics g = Graphics.FromImage(pictureBox1.Image);
Pen myPen = new Pen(Color.Black);
g.DrawLine(myPen, 0, 0, 100, 100);
myPen.Dispose();
g.Dispose();
Invalidate();
}
}
}
Nobody's stopping you from drawing wherever you want, the thing to remember is to do it on a bitmap image instead of trying to force it to screen. By this I mean you need to create your own Bitmap object, and any draw functions you call you call them on this.
To actually get this to show on screen, call the Invalidate function on the host control -- not a PictureBox by the way. Something lightweight like a panel will do. And in that control's Paint event simply draw your image.
You were sort of close, but you calling Invalidate on the form, besides the fact that it's horribly inefficient to redraw everything when you know exactly what needs to be redrawn, simply won't do anything. You don't rely on the Paint event, but on the intrinsic binding a PictureBox has with a Bitmap -- Bitmap who's handle you never change. As far as the PictureBox is concerned, everything is the same so it won't actually paint itself again. When you actually force it to paint itself by dragging the window outside the screen bounds and then back in it will read the bitmap and draw what you expect.
You must draw in the Paint event of the picture box. Windows might trigger the Paint event at any moment, e.g. if a window in front of it is removed or the form containing the picturebox is moved.
What you draw on the screen is volatile; therefore, let Windows decide when to (re-)draw. You can also trigger redrawing with
picturebox1.Invalidate();
or
picturebox1.Refresh();
Difference: Invalidate waits until the window is idle. Refresh draws immediately.
If you only draw in Form_Load or Button_Click, your drawing might get lost, when windows triggers a paint on its own. Try calling picturebox1.Invalidate in these events instead.
I've written a Windows Forms app where I do custom drawing on a Panel using Control.CreateGraphics(). Here's what my Form looks like at startup:
The custom drawing is performed on the top panel in the Click event handler of the "Draw!" button. Here's my button click handler:
private void drawButton_Click(object sender, EventArgs e)
{
using (Graphics g = drawPanel.CreateGraphics())
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
After a click on drawButton, the form looks like this:
Success!
But when I shrink the form by dragging a corner...
...and expand it back to its original size,
part of what I drew is gone!
This also happens when I drag part of the window offscreen...
...and drag it back onscreen:
If I minimize the window and restore it, the whole image is erased:
What is causing this? How can I make it so the graphics I draw are persistent?
Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.
TL;DR:
Don't do your drawing in response to a one-time UI event with Control.CreateGraphics. Instead, register a Paint event handler for the control on which you want to paint, and do your drawing with the Graphics object passed via the PaintEventArgs.
If you want to paint only after a button click (for example), in your Click handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate(). Then do your rendering conditionally in the Paint handler.
Finally, if your control's contents should change with the size of the control, register a Resize event handler and call Invalidate() there too.
Example code:
private bool _doCustomDrawing = false;
private void drawPanel_Paint(object sender, PaintEventArgs e)
{
if (_doCustomDrawing)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
private void drawButton_Click(object sender, EventArgs e)
{
_doCustomDrawing = true;
drawPanel.Invalidate();
}
private void drawPanel_Resize(object sender, EventArgs e)
{
drawPanel.Invalidate();
}
But why? What was I doing wrong, and how does this fix it?
Take a look at the documentation for Control.CreateGraphics:
The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.
Windows doesn't take responsibility for retaining the graphics you draw to your Control. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint method, which you can override if you subclass Control or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint event, which a control will fire near the end of its OnPaint method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.
Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate(). Other situations may require only a partial repaint. If Windows determines that only part of a Control needs to be repainted, the PaintEventArgs you receive will have a non-empty ClipRegion. In this situation, your drawing will only affect the area in the ClipRegion, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate() was required in the above example. Because the appearance of drawPanel needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.
I have a custom user control which displays waveform of an audio file. I've put two instances of controls on a form. The second instance works as expected while the first instance causes the problem mentioned.
What I am doing is drawing a vertical (red) line, indicating current position. The problem is best seen in a youtube video.
This is the code of my custom controller (OnPaint() - notice that I invalidate only the region affected by the red vertical line):
protected override void OnPaint(PaintEventArgs e)
{
[...]
Invalidate(new Rectangle(x_pos-5, 0, x_pos, this.Height));
using (Pen linePen = new Pen(Color.Red, 1.5f))
{
e.Graphics.DrawLine(linePen, x_pos, 0, x_pos, this.Height);
Invalidate(new Rectangle(x_pos-2,0,x_pos+2,this.Height));
}
base.OnPaint(e);
}
Q: Since the OnPaint method is equivalent for both controls why do I need to move the window to repaint the first control (waveform)?
The problem with OnPaint is that it's only called when it's necessary, for example when the window is moved, resized, after it's brought back from minimized state, or when another window is moved on top of it.
In order to periodically repaint the window (or parts of it), you'll need to add a Timer to the form and implement its Tick event.
private void timer1_Tick(object sender, EventArgs e)
{
this.Invalidate();
}
By default, Timer.Interval is set to 100 (100 ms). If you only want to update your rectangle every second, you can increase that value to 1000 if you want.
Excuse the code dump, these are functions within a UserControl
private void PNGQuantPreviewControl_Resize(object sender, EventArgs e)
{
createOffScreenBm();
draw();
}
private void createOffScreenBm()
{
offScreenBm = new Bitmap(this.Size.Width, this.Size.Height);
offScreenGfx = Graphics.FromImage(offScreenBm);
}
private void draw()
{
// draw background
offScreenGfx.FillRectangle(transTexture, 0, 0, offScreenBm.Width, offScreenBm.Height);
// draw image preview
offScreenGfx.DrawImage(pngQuantPreview, getTopLeftPosition());
// apply to picture box
this.CreateGraphics().DrawImage(offScreenBm, 0, 0);
}
So, when the control changes size, it recreates the offscreen bitmap to reflect the new size and redraws the image.
However, if I quickly resize the control the bitmap doesn't fill it, there's a gap left at the right and/or bottom.
I'm fairly new to C#, so there's probably something obvious I'm doing wrong, or I'm reading the size values at the wrong time. Any ideas?
First of all you need to overwrite OnPaint method, or subscribe to Paint event and draw everything there.
Second you do not need to create offscreen bitmap for double buffering, because in .net already exist class for such purposes BufferedGraphics.
And third, it is much better to create UserControl descedant and enable internal .net double buffering, something like this:
public UserControl2
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
Using this approach you will get double-buffering, and all you need is to draw your graphics in OnPaint method. You can read more about this control styles in Msdn.
Have you considered overriding the OnPaint method and placing the code within that method? This would result in your drawing code being executed any time the control needs to be redrawn, regardless of the reason.
A resize event does not necessarily wait until you are finished resizing the parent container. When the resize event is raised it needs to wait until the code exits before it can capture a new resize event so when the window/control is resized quickly, it can't keep up all that well and what you get is the last time it was able to capture the event, not necessarily the final state of the control ... if that makes any sense.
Do you have anything like a splitter on your control, or a MinSize or MaxSize declared?