GDI+ drawing becomes corrupted when ClientSize changes - c#

I need to draw a circle with the size of the entire form. I have made a Form1 with the following Paint event handler:
private void Form1_Paint(object sender, PaintEventArgs e)
{
SolidBrush myBrush = new SolidBrush(Color.Black);
e.Graphics.FillEllipse(myBrush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
The problem is that when you change the size of the form or maximize it the drawing becomes corrupted. Please help me how to avoid this problem. Sorry I haven't found an appropriate topic on StackOverflow.

Painting for container controls like Form was optimized, they only redraw the part of the window that was revealed by the resize. In other words, if the user drags the right or bottom edge then there's no repaint at all when the window is made smaller. A small repaint if it is made bigger, only the part of the window to became visible.
This is normally very appropriate since they don't have much reason to completely repaint themselves, they only draw their background. This matters in particular when the user resizes the window by dragging a corner, that can generate a storm of repaint requests. And if repainting is slow then that gets to be very visible, moving the mouse makes the motion "stutter", flicker can be noticeable as well.
But you care, you need to completely redraw the ellipse since you use the ClientSize property in your paint event handler.
Whether or not this optimization is turned on is determined by a style flag for the control, as Sriram pointed out. Turning this off is so common that Winforms exposed the style flag through a simple property to make it easier to change it:
public Form1() {
InitializeComponent();
this.ResizeRedraw = true;
this.DoubleBuffered = true;
}
Note the DoubleBuffered property, another example of a convenience property that actually changed ControlStyles. You want this turned on to suppress the flicker your window exhibits. Caused by first drawing the background, which erases your ellipse, then repainting the ellipse.
There's another pretty serious problem in your code, SolidBrush is a disposable class. Disposing objects is optional, but skipping this for System.Drawing objects is quite unwise. If you resize your window frequently enough, 10000 times, then your program will crash. That sounds like a lot, but it is not when you set ResizeRedraw to true since that generates a lot of repaints. Only the garbage collector can keep you out of trouble, it won't in a simple program like this. Fix:
protected override void OnPaint(PaintEventArgs e) {
using (var brush = new SolidBrush(this.ForeColor)) {
e.Graphics.FillEllipse(brush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
base.OnPaint(e);
}
Btw, do not optimize this by keeping the brush around as recommended in another post. Creating a brush is very cheap, takes about a microsecond. But is far too expensive to keep around, it is allocated on a heap that's shared by all programs that run on the desktop. A limited resource, that heap is small and programs start failing badly when the that heap is out of storage.

You'll have to enable ResizeRedraw. Add the following to your constructor.
this.SetStyle(ControlStyles.ResizeRedraw, true);

On Forms Resize event paste this ;
this.Refresh();
this will redraw the form and fire the Form1.Paint event.

That occurs because only part of the form's area gets invalidated when the size changes. What you need to do is handle the SizeChanged event and call the form's Refresh method.

Call Graphics.Clear(); before drawing to clear the old graphics and avoid creating redundant brushes.Put the code for the Brush creation outside the block
SolidBrush myBrush = new SolidBrush(Color.Black);
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear();
e.Graphics.FillEllipse(myBrush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}

Related

Winforms: How to draw line over control without invalidating it

i have a class that draws waveforms of audio. I'm drawing it in OnPaint function. Now i need to draw a line that shows where on the waveform we are at current moment. I can calculate the position but when i try to draw it i need to call Invalidate() and that forces form to redraw all that waveform chart data (a lot of points).
So is there a way to put some transparent element over this one and then call Invalidate() only on that element? i was trying with picture box but no sucess...
//main form
private void timer100ms_Tick(object sender, EventArgs e)
{
customWaveViewer1.currentPosition = (int)((stream.Position / (float)stream.Length) * customWaveViewer1.Width);
customWaveViewer1.overlayLabel.Invalidate(false);
}
//drawing function in my class
private void overlayLabelInvalidate(object sender, PaintEventArgs e)
{
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));
e.Graphics.DrawLine(pen, currentPosition, 0, currentPosition, this.Height);
}
//constructor
public CustomWaveViewer()
{
InitializeComponent();
this.DoubleBuffered = true;
this.PenColor = Color.DodgerBlue;
this.PenWidth = 1;
overlayLabel = new PictureBox();
overlayLabel.Size = new System.Drawing.Size(this.Width, this.Height);
overlayLabel.Location = new Point(this.Left, this.Top);
overlayLabel.Visible = true;
overlayLabel.BackColor=Color.FromArgb(0,0,0,0);
overlayLabel.Paint += new System.Windows.Forms.PaintEventHandler(this.overlayLabelInvalidate);
Controls.Add(overlayLabel);
}
Actually what you are saying is not exactly true.
In the painteventargs there is a rectangle indicating the small portion of the window that needs to be repainted.
Also when you invalidate, you don't necessarily need to invalidate the whole form.
In your case you might want to invalidate only the old and new position of the marker that indicates where you are in the waveform.
So in your algorithm of your paint method, it is really up to you to make it efficient and only paint that part of the window that really needs repainting and to skip the part that does not need repainting.
It really can make a huge difference.
To make it even more look professional, set double buffering on.
No need to hastle with bitmaps of the whole image you have yourself, that is just what double buffering is all about, and forms can do it for you.
I copied following excerpt from https://msdn.microsoft.com/en-us/library/windows/desktop/dd145137(v=vs.85).aspx
BeginPaint fills a PAINTSTRUCT structure with information such as the dimensions of the portion of the window to be updated and a flag indicating whether the window background has been drawn. The application can use this information to optimize drawing. For example, it can use the dimensions of the update region, specified by the rcPaint member, to limit drawing to only those portions of the window that need updating. If an application has very simple output, it can ignore the update region and draw in the entire window, relying on the system to discard (clip) any unneeded output. Because the system clips drawing that extends outside the clipping region, only drawing that is in the update region is visible.
In this case there is no simple output and taking this into account is adviced.
I am not saying that creating a bitmap will not work. I am saying that optimizing your drawing logic will solve it just as well.
The information above still stands as windows forms is built on top of the old win32 api.

Graphics in Windows Forms

When the form is loading shouldn't the CreateGraphics() return a graphics object?
I mean, on the Form1_Load event can I write for example the following?
Graphics x;
private void Form1_Load(object sender, EventArgs e)
{
x = this.CreateGraphics();
}
If not, then WHY?
I thought that when you create a new form, the constructor initiates the all object of the form. So why not also the graphics object?
I'm asking that, because when I'm trying to draw - on form_load, it doesn't show me what I draw.
The main reason is: I want to create a game, which have a board - so when the user click on the new game - first - I'm initiating the board and drawing it.
And in the onPaint event I just want to draw the current state of the board. So I thought the the initial state of the board should be draw on the formLoad event.
You should not use the Graphics object in this manner; you should enclose each usage of it in a using block, or otherwise make sure you dispose of it after each set of drawing operations. Your code here will leave an unnecessary Graphics object hanging around.
Brief example:
private void MyonPaintOverload()
{
using(Graphics x = this.CreateGraphics())
{
// draw here...
}
}
Also, drawing on Form_Load() won't work, because the window is not actually visible at that point; there's nothing to draw on, basically.
Yes, you generally need to redraw the whole thing each cycle - because something as simple as another window being dragged across your window will 'wipe out' your drawing, and when it's invalidated by the other window being moved away, you need to redraw everything that you manually drew.

When I minimize my application all of my drawings disappears

I am building a C# app that shows the trajectory of a cannonball. But when I minimize it the trajectory I have drawn to a picture box are gone when I bring it back up. Is there an easy way to stop this?
I bet your drawing in the mouse event. Use the onpaint event and you should be good to go.
Edit:
Here is a decent drawing tutorial using the onpaint() event:
http://www.geekpedia.com/tutorial50_Drawing-with-Csharp.html
When the window is restored it will need to redraw the form. If you do not have your drawing as part of the paint event, then it will not be redrawn.
This question is very similar to this one
Saving a Graphics content to a file
As the others have already stated the problem is when you draw onto a graphics object, there is nothing retained. It is called persistent graphics. Sometimes you want this behavior, more often than not you don't.
You should do your drawing onto a bitmap, then copy the bitmap to your picturebox.Image. The other option as stated in the other answers, is do your drawing routines in the OnPaint Method.
Read my answer in the above. The title is misleading, he thought he had to save to a file to gain persistence, but we showed him otherwise.
EDIT Here is the important code from the above link
Bitmap buffer;
public Form1()
{
InitializeComponent();
panel1.BorderStyle = BorderStyle.FixedSingle;
buffer = new Bitmap(panel1.Width,panel1.Height);
//Make sure you resize your buffer whenever the panel1 resizes.
}
private void button1_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(buffer))
{
g.DrawRectangle(Pens.Red, 100, 100,100,100);
}
panel1.BackgroundImage = buffer;
}
i was having same problem just used mainp.refresh() after event change
mainp was my panel in which i was drawing my all graphics

GDI drawing application with high CPU usage

I have an application where the user draws some shapes.
When I click over a shape and I drag it, the CPU goes 100% because of Invalidate() inside MouseMove.
If I a use a timer and call Invalidate() from tick event the moving is not so smooth.
Is there any other approach to minimize CPU and have smooth moving?
` Point startDragMousePoint;
Point startShapeLocation;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if(isMouseDown)
{
Point deltaPoint = Point.Subtract(e.Location, new Size(startDragMousePoint));
shape.Location = Point.Add(startShapeLocation, new Size(deltaPoint));
Invalidate();
}
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
shape.Render(e.Graphics);
}`
There are three general solutions.
1) Don't draw while your moving, this was the solution in windows for a long time, when you dragged a window, it just disapeard and you saw the outline of a window.
2) Create a bitmap object and only move that. Note you will have to redraw the area under it.
3) Don't invalidate the hole window, just the area you are changing. Drawing to a buffer (a bitmap) can help you reuse areas.
Also, if GDI isn't the fastest drawing functions in the world. If your shape is very complex, you might want to consider using OpenGL, DirectX or SDL.
Instead of invalidating the entire area you could invalidate the portion of the control that has changed by using:
Rectangle changedArea = new Rectangle(cX, cY, cW, cH);
this.Invalidate(changedArea);
Also make sure your control is set to use DoubleBuffering
this.DoubleBuffered = true;
From the limited code that you have put up, I think the invalidate will not cause any problem. Most probably the problem may be inside the real rendering code of yours shape.Render(). In the past I have written similar application where I have called Invalidate in the mouse move and the applications has worked fine. Only there were some flickering which is gone on enabling double buffering.

Why do my WinForms controls flicker and resize slowly?

I'm making a program where I have a lot of panels and panels in panels.
I have a few custom drawn controls in these panels.
The resize function of 1 panel contains code to adjust the size and position of all controls in that panel.
Now as soon as I resize the program, the resize of this panel gets actived. This results in a lot of flickering of the components in this panel.
All user drawn controls are double buffered.
Can some one help me solve this problem?
To get rid of the flicker while resizing the win form, suspend the layout while resizing. Override the forms resizebegin/resizeend methods as below.
protected override void OnResizeBegin(EventArgs e) {
SuspendLayout();
base.OnResizeBegin(e);
}
protected override void OnResizeEnd(EventArgs e) {
ResumeLayout();
base.OnResizeEnd(e);
}
This will leave the controls intact (as they where before resizing) and force a redraw when the resize operation is completed.
Looking at the project you posted, the flickering is really bad when you have the first tab selected, with the gradient-filled group boxes. With the second or third tab showing, there's hardly any flicker, if at all.
So clearly the problem has something to do with the controls you're showing on that tab page. A quick glance at the code for the custom gradient-filled group box class gives away the more specific cause. You're doing a lot of really expensive processing each time you draw one of the groupbox controls. Because each groupbox control has to repaint itself each time the form is resized, that code is getting executed an unbelievable number of times.
Plus, you have the controls' background set to "Transparent", which has to be faked in WinForms by asking the parent window to draw itself first inside the control window to produce the background pixels. The control then draws itself on top of that. This is also more work than filling the control's background with a solid color like SystemColors.Control, and it's causing you to see the form's pixels being drawn while you resize the form, before the groupboxes have a chance to paint themselves.
Here's the specific code I'm talking about from your custom gradient-filled groupbox control class:
protected override void OnPaint(PaintEventArgs e)
{
if (Visible)
{
Graphics gr = e.Graphics;
Rectangle clipRectangle = new Rectangle(new Point(0, 0), this.Size);
Size tSize = TextRenderer.MeasureText(Text, this.Font);
Rectangle r1 = new Rectangle(0, (tSize.Height / 2), Width - 2, Height - tSize.Height / 2 - 2);
Rectangle r2 = new Rectangle(0, 0, Width, Height);
Rectangle textRect = new Rectangle(6, 0, tSize.Width, tSize.Height);
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(r2);
gp.AddRectangle(r1);
gp.FillMode = FillMode.Alternate;
gr.FillRectangle(new SolidBrush(Parent.BackColor), clipRectangle);
LinearGradientBrush gradBrush;
gradBrush = new LinearGradientBrush(clipRectangle, SystemColors.GradientInactiveCaption, SystemColors.InactiveCaptionText, LinearGradientMode.BackwardDiagonal);
gr.FillPath(gradBrush, RoundedRectangle.Create(r1, 7));
Pen borderPen = new Pen(BorderColor);
gr.DrawPath(borderPen, RoundedRectangle.Create(r1, 7));
gr.FillRectangle(gradBrush, textRect);
gr.DrawRectangle(borderPen, textRect);
gr.DrawString(Text, base.Font, new SolidBrush(ForeColor), 6, 0);
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (this.BackColor == Color.Transparent)
base.OnPaintBackground(pevent);
}
And now that you've seen the code, red warning flags ought to go up. You're creating a bunch of GDI+ objects (brushes, pens, regions, etc.), but failing to Dispose any of them! Almost all of that code should be wrapped in using statements. That's just sloppy coding.
Doing all of that work costs something. When the computer is forced to devote so much time to rendering controls, other things lag behind. You see a flicker as it strains to keep up with the resize. It's no different than anything else that overloads a computer (like a computing the value of pi), it's just really easy to do so when you use as many custom drawn controls like you do here. Transparency is hard in Win32, and so is a lot of custom 3D painting. It makes the UI look and feel clunky to the user. Yet another reason that I don't understand the rush away from native controls.
You really only have three options:
Deal with the flicker. (I agree, this is not a good option.)
Use different controls, like the standard, built-in ones. Sure, they may not have a fancy gradient effect, but that's going to look broken half of the time anyway if the user has customized their Windows theme. It's also reasonably hard to read black text on a dark gray background.
Change the painting code within your custom controls to do less work. You may be able to get by with some simple "optimizations" that don't cost you any of the visual effects, but I suspect this is unlikely. It's a tradeoff between speed and eye candy. Doing nothing is always faster.
I successfully eliminate flicker when form resize using this code. Thanks.
VB.NET
Public Class Form1
Public Sub New()
Me.SetStyle(ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.SupportsTransparentBackColor, True)
End Sub
Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
Me.Update()
End Sub
End Class
C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Resize += Form1_Resize;
this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor, true);
}
private void Form1_Resize(object sender, System.EventArgs e)
{
this.Update();
}
}
So I ran into this same problem - my control with a transparent background was repainting like 34 times, and what worked for me was:
On my form that contained the control
protected override void OnResize(EventArgs e)
{
myControl.Visible = false;
base.OnResize(e);
myControl.Visible = true;
}
And the same in the control:
protected override void OnResize(EventArgs e)
{
this.Visible = false;
base.OnResize(e);
this.Visible = true;
}
This reduced the amount of repainting to 4, which effectively eliminated any flicker when the control was being resized.
Maybe a good solution for you will be to use Form.ResizeBegin and Form.ResizeEnd events.
On ResizeBegin set main panel visibility to false, on ResizeEnd set main panel visibility to true.
This way panels will not be redrawn while someone is resizing your form.
While hooking into ResizeBegin and ResizeEnd is the right idea, instead of hiding the main panel's visibility I'd instead delay any resize calculations until ResizeEnd. In this case, you don't even need to hook into ResizeBegin or Resize - all the logic goes into ResizeEnd.
I say this for two reasons. One, even though the panel is hidden, the resize operation will likely still be expensive and so the form won't feel as responsive as it should unless the resize calculations are delayed. Two, hiding the pane's contents while resizing can be jarring to the user.
I had the same problem.
It seams that this is happening because you are using rounded corners. When I set CornerRadius property to 0, the flickering was gone.
So far I have only found the following workaround. Not the nicest one, but it stops the flickering.
private void Form_ResizeBegin(object sender, EventArgs e)
{
rectangleShape.CornerRadius = 0;
}
private void Form_ResizeEnd(object sender, EventArgs e)
{
rectangleShape.CornerRadius = 15;
}
I Had A Similar Issue Like This. My Entire Form Was Resizing Slowly And The Controls Painted Them In An Ugly Manner. So This Helped Me:
//I Added This To The Designer File, You Can Still Change The WindowState In Designer View If You Want. This Helped Me Though.
this.WindowState = FormWindowState.Maximized;
And In The Resize Event, Add This Code To The Beginning
this.Refresh();

Categories

Resources