Question:
How do you properly draw on a winform from a method other than the OnPaint() method?
Additional Information:
The code I have now draws some background lines for a TicTacToe game in the OnPaint() method. Then I use the Mouse_Click event and am running this code which apparently is not proper:
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
Graphics g = this.CreateGraphics();
g.DrawEllipse(this.penRed, this.Rectangle);
For reasons I do not understand, it does draw the circle, but when minimizing or moving the form off screen it erases the circles but not the lines from the OnPaint() method.
You are doing a lot of "view" but no "model".
When you want to create a shape, when the mouse button goes down/up, create some DATA representing the shape.
Your data structures represent the persistent information (it is the data that allows you to save and load this information between sessions).
All your paint function needs to do is look at the DATA structures and paint it. This will therefore persist between sizing/hiding/showing.
The problem is that Windows windows (that includes WinForms) have no graphical memory of their own unless their creator provides such memory and it is only a matter of time before that particular window wilk get overwritten or hidden and eventually need to be repainted. You're painting to the screen ( you might say ) and others can do the same. The only convention you can rely on is that the OnPaint will get called when needed. Basically it's alright to use your philosophy and draw whenever you need to (not on some misterious and unpredictable schedule). For that check out my solution.
You should use a "backbuffer bitmap" like:
private Bitmap bb;
protected override void OnResize(EventArgs e) {
this.bb = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
this.InitBackBuffer();
}
private void InitBackBuffer() {
using (var g = Graphics.FromImage(this.bb)) {
// do any of the "non dissapearing line" drawing here
}
}
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
using (Graphics g = Graphics.FromImage(this.bb))
g.DrawEllipse(this.penRed, this.Rectangle);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.DrawImageUnscaled(this.bb);
}
Try that. That should do it :)
What you are doing is drawing on the form "asynchronously" (from the OnPaint method). You see, the OnPaint method is what Windows Forms relies on to draw your entire form. When something happens to your From, it is invalidated and OnPaint is called again. If something isn't drawn in that method, then it will not be there after that happens.
If you want a button to trigger something to appear permanently then what you need to do is Add that object to a collection somewhere, or set a variable related to it. Then call Refresh() which calls Invalidate() and Update() Then, during OnPaint, draw that object (ellipis).
If you want it to still be there after something happens to your form, such as minimize, you have to draw it during OnPaint.
Here's my suggestion:
public partial class Form1 : Form
{
Rectangle r = Rectangle.Empty;
Pen redPen = new Pen(Color.Red);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
r = new Rectangle(50, 50, 100, 100);
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (r != Rectangle.Empty)
{
e.Graphics.DrawRectangle(redPen, r);
}
}
}
Related
To clearup what I meant by my question.. I got used when making a game in C# using the .NET framework, to implement my own DrawScene() method and call it whenever I want to redraw the game's graphics (basically after any instance in the game has moved or changed its shape/state), like the following:
private void DrawScene()
{
Graphics g = this.CreateGraphics();
g.Clear(Color.Black);
g.DrawImage(myBitmap, 0, 0);
g.Dispose();
}
And by doing so, in my Paint event handler, all I do is the following:
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawScene();
}
and for example, also when I want to redraw after the player makes some move, I simply call DrawScene() the same way:
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Keycode == Keys.Up)
{
hero.y -= 5;
}
DrawScene();
}
Is there any serious difference between doing that or doing it this way (Which I notice most people follow):
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
e.Graphics.DrawImage(myBitmap, 0, 0);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Keycode == Keys.Up)
{
hero.y -= 5;
}
this.Invalidate();
}
Sorry if this was much talking, but I just wanted to clear things up about my question..
So, if the two cases above does the drawing exactly the same way (From the Graphics viewpoint), and there's no harm keeping on my usual implementation as in the first case.. Then when is it best to use the invalidate() method?
This two approaches differs seriously (second one is preferred).
First of all - in your approach you are creating new Graphics object each time you need to paint something, while in Paint event handler you're using the same graphics object each time.
This increases memory usage (since redraws can occurs very frequently), and also since you're not disposing graphics you're creating (basically you should do it with any object implementing IDisposable you no longer need to use) - system resources used by graphics not being freed.
Also, your form can be redrawed not manually by your call, but in some other cases (overlapping by other window, show/hide and so on). If your paintings will be done in Paint handler - then they will be done automatically, but in your "first" approach whey will not until you manually call your drawing method.
So basically it is always better to paint everything in Paint event handlers (and call Invalidate or even Refresh to force redrawing) and not manually in separate methods.
Sometimes (if your drawing takes much code and can be logically splitted into parts) it can be better to handle it like:
private void DrawScene(Graphics g)
{
g.Clear(Color.Black);
g.DrawImage(myBitmap, 0, 0);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawScene(e.Graphics);
DrawSomething(e.Graphics);
}
So you still using grahics object from Paint event handler, but just passing it into drawing methods.
i am creating a drawing programe which Suppose to take the parameters from the user
(Radius,height,width,....etc)
i have created a class with one paramter (radius)
public int faceoutline(int r)
{
Graphic = pictureBox1.CreateGraphics();
Graphic.DrawEllipse(myPen, 0, 0, r, r);
return r;
}
then i call it :
private void button1_Click(object sender, EventArgs e)
{
faceoutline(int.Parse(textBox1.Text));
pictureBox1.Invalidate();
}
....and nothing happens
i copied the button code to a timer but it keep balnking
WHAT I AM DOING WRONG ?!
You should place the drawing code in the Paint event handler of the PictureBox, and use the Graphics provided in the event args. This way, your custom drawing code will be executed every time the control is redrawn.
If you don't do it this way, anything you draw will disappear the next time the control is redrawn. In your code, you call Invalidate right after you draw your ellipse, so the control is redrawn without the ellipse...
Relatively new to C#; hopefully I'm just overlooking something simple.
I have a form named 'Exercise1' which contains a picture box called 'drawingArea' and a few buttons. The code for the constructor of Exercise1 is as follows:
public Exercise1()
{
InitializeComponent();
paper = drawingArea.CreateGraphics();
balloon = new Balloon("redBalloon", Color.Red, drawingArea.Width / 2,
drawingArea.Height / 2, 30);
paper.Clear(Color.White);
balloon.Display(paper);
}
...
'paper' and 'balloon' are created as globals above the constructor for use in the other methods on the form. Both 'paper' and 'balloon' work as initialized in the constructor in the other methods defined on the form.
For whatever reason, the commands
paper.Clear(Color.White);
and
balloon.Display(paper);
Which should clear the picture box and show a red ellipse, don't execute (at least visibly). What gives?
UPDATE:
Think I'm going to like this website... You guys are quick!
#Nitesh: The constructor for Exercise1 is called from another form. Code is as follows:
private void button1_Click(object sender, EventArgs e)
{
int exSelector = (int)numericUpDown1.Value;
switch (exSelector)
{
case 1:
Exercise1 form1 = new Exercise1();
form1.Show();
break;
...
#Sean Dunford: Yes and yes it is.
#RBarryYoung: Was playing around with that a bit, but had no luck. What command triggers a Form_Load event for Exercise1?
UPDATE: This altered code works as expected:
public Exercise1()
{
InitializeComponent();
paper = drawingArea.CreateGraphics();
drawingArea.BackColor = Color.White;
drawingArea.Paint += new PaintEventHandler(this.drawingArea_Paint);
balloon = new Balloon("redBalloon", Color.Red, drawingArea.Width / 2, drawingArea.Height / 2, 30);
}
private void drawingArea_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
balloon.Display(e.Graphics);
}
...
Thanks for all the help!
You cannot do drawing in the constructor. To do proper drawing, you need to have the form shown on the screen. You can try using the Shown event to do your rendering (this may get lost when the form is redrawn, though).
Usually the best way is to set whatever flags you need in the constructor and then use the Paint event of the form to do all painting. Later on, when you need to repaint something, set up whatever state needs to be rendered, invalidate your form (this results in a Paint event) and then you can repaint the new state.
If you try to do customized drawing (outside your Paint event) you'll run the risk of things randomly going blank or your drawing may disapper when you resize/minimize your form.
You use Graphics in a constructor, that means that you draw on the paper only once, any redraw for whatever reason that happens after constructor will draw the drawingArea in its original way. Try to add PaintEventHandler to drawingArea and then call inside balloon.Display(e.Graphics);
public Exercise1()
{
InitializeComponent();
balloon = new Balloon("redBalloon", Color.Red, drawingArea.Width / 2,
drawingArea.Height / 2, 30);
drawingArea.Paint += new PaintEventHandler(drawingArea_Paint);
}
void drawingArea_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
baloon.Display(e.Graphics);
}
You should be overriding the forms OnPaint event handler. In doing so, you are able to get the graphics context which will redraw your paper and balloon areas.
I have a method where I disable or enable some custom controls and then use a graphics object to draw lines and rectangles.
The gist of the method:
void MyMethod()
{
//...
mycontrol.enabled = false;
mycontrol.visible = false;
mycontrol.Invalidate();
mycontrol.Update();
GraphicsObject.DrawLines();
//...
}
Right after this method returns, the screen looks great. I have rectangles and lines where controls used to be.
However, after the click event handler returns (which called the above method). The controls which should be invisible draw over the lines and rectangles (leaving those area's blank - the same color as the background form).
Is there any way to fix this?
Thanks
As I mentioned in my comment if you are drawing on an object if you do not use the OnPaint Method or the Paint Event your custom drawing will not be automatically redrawn. Depending on what you are drawing on you can do something like( I am assuming you are drawing on a Form).
void MyMethod()
{
//...
mycontrol.enabled = false;
mycontrol.visible = false;
mycontrol.Invalidate();
mycontrol.Update();
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
//Conditional Logic to determine what you are drawing
// myPoints is a Point array that you fill elsewhere in your program
e.Graphics.DrawLines(new Pen(Brushes.Red), myPoints);
}
I want to display graphic file in PictureBox I have:
private void btnLoad_Click(object sender, EventArgs e)
{
if (dgOpenFile.ShowDialog() == DialogResult.OK)
{
Bitmap img = new Bitmap(dgOpenFile.FileName);
picture.Width = img.Height;
picture.Height = img.Height;
g.DrawImage(img, 0f, 0f);
}
}
That's g
private void Form1_Load(object sender, EventArgs e)
{
g = picture.CreateGraphics();
}
But when I move my Form outside the window my picture disappears. How can I prevent that?
You should do any custom drawing in the OnPaint event of the control to make it persistent. This causes your drawing to be redrawn every time the control is painted.
However, in this case it would be easier to use the picture box as it was designed:
picture.Image = img;
Windows uses a Paint-on-Request principle.
So when it sends a WM_PAINT message to your Control, it's OnPaint() is called. You should be ready to draw the image (again) in an overridden OnPaint() or in a Paint event handler.
But a Picturebox will do all this for you.