OnPaint does not update - c#

I have a windows form which contains a user control. This user control has the following code:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
pe.Graphics.DrawRectangle(
new Pen(Color.Red, 5 + laenge),
new Rectangle(
new Point(50 + leerzeichen, hoehe),
new Size(laenge + 20, 20)));
}
and some more code, which is probably not important now. So when I start the programm it draws the red rectangle. All the variables (laenge, leerzeichen, hoehe) are set to 0 at the beginning of the program. Now, when I press a button the variables are changing, but OnPaint does not draw the new rectangle? What could be the problem? Do I have to call OnPaint in some way?

You need to call Invalidate(), after changing variables (it calls OnPaint internaly)

You do not call OnPaint directly.
Rather, as inherited from Win32 (InvalidateRect()), the control's region needs to be invalidated (e.g. by calling Invalidate()) to cause Windows to call the control's OnPaint method during refresh. (see this question)
Be aware that the OS can process paint/refresh requests only when the application waits for the Windows message queue (i.e. it is done processing user requests, or calls Application.DoEvents()).

Related

Calling a function that involves PaintEventArgs when a button is clicked

Sorry if this has been asked before.
I was working on a simple connect 4 game in C# windows forms as I haven't done any work involving graphics before. To do this I need the program to draw circles when a button is pressed however I don't know how to call the function to do so.
public void printToken(PaintEventArgs pe, int x)
{
Graphics g = pe.Graphics;
Pen blue = new Pen(Color.Blue);
Pen red = new Pen(Color.Red);
Rectangle rect = new Rectangle(50 + ((x-1) * 100), 50, 50, 50);
g.DrawEllipse(blue, rect);
}
private void button1_Click(object sender, EventArgs e)
{
printToken(null, 1);
}
The null in place is just a placeholder as obviously that will not work.
Any and all help is appreciated.
Typically in a Windows Forms application where you want to do custom drawing, you either draw directly on a Form or PictureBox in the Paint event handler or create a subclass of Control in which you override the OnPaint method. In the Paint event handler or OnPaint, you draw everything (i.e. not just one circle, but all the circles). Then when your underlying data structure changes, indicating that you need a repaint, you call Invalidate() on the control, which marks it as needing redraw, and on the next pass through the event loop, your Paint event handler will run or your OnPaint method will be called. Within that method, you'll have the PaintEventArgs object you need to get a Graphics object with which to do your drawing. You don't want to "draw once and forget" (e.g. when a button is clicked) because there are all sorts of things that can cause your control to need to repaint itself. See this question and answer for more details on that.
Edit: here's some hand-holding in response to your comment. (It was going to be a comment but it got too long.)
If I assume for the moment that you're starting with a blank Form in Visual Studio's Windows Forms Designer, the quickest way to get going would just be to select the Form, and in VS's Properties pane, click the lightning bolt toolbar button to view the form's events. Scroll down to the Paint event and double-click anywhere in the blank space to the right of the label. That will wire up a Paint event handler for the form and take you to the newly added method in the form's code file. Use the PaintEventArgs object called e to do your drawing. Then, if you need to change what gets drawn upon some button click, in your click handler change the data that determine what gets drawn (e.g. positions and colors of of the playing pieces) and, when you're done, call Invalidate().
If you add a UserControl representing a single chip to your form, and override the OnPaint method and put the coloring code into that event, then the Graphics object will not be null when the Paint event fires.
To kick off the paint of the UserControl, either call the Refresh method or InvalidateRectangle method - either will fire the OnPaint method.
Make sure to call up the class hierarchy to the OnPaint first, to make sure that the background of the object is drawn first:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
Pen blue = new Pen(Color.Blue);
Pen red = new Pen(Color.Red);
Rectangle rect = new Rectangle(50 + ((x-1) * 100), 50, 50, 50);
g.DrawEllipse(blue, rect);
}

Drawing glitches when using CreateGraphics rather than Paint event handler for custom drawing

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.

User control repainting only when window moved

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.

Form1_Paint event

i wonder how it works..
i have this code. it should paint pictures (Bitmap images ) on the form..constantly. but i dont know how often it is triggered. i need it to be triggered very often (at least every 1-2 seconds). i need it to send the parameters to another object that i have (Game game).. so game object will draw everything
public void Form1_Paint(object sender, EventArgs e)
{
//the animation has 4 cell to draw, so the arguments are passed to the game objects instructing it to which cells to draw.
using (Graphics g = this.CreateGraphics())
{
game.Draw(g, animationTimerCounter);
}
}
when is the event being fired?
A paint event is called when the form (or part of it) has to be redrawn, e.g. the form is moved, or another window has hidden a part of it etc.
You can force a Paint event by calling yourControl.Invalidate() method.
In your case you could use for example a Timer to force a Paint with the desired frequency (e.g. every 1-2 seconds).
Paint is basically triggered as required, events that trigger paint can be examples such as (there are others):
A form that was in front is moved
You form resizes
your form is restored
Is this XNA? it kinda looks like it is a similar effect. XNA is a good framework for what it looks like you're trying to do.
If you need your form to paint, you can invalidate it or tell it to paint.
The Paint event is raised when the control is redrawn. Control.Paint Event in MSDN

.NET UserControl: Size property gives incorrect value on Resize event

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?

Categories