I'm having a problem with refreshing graphics on panels and forms.
When I draw the image it works fine but when I want to replace it with another image using Panel.Refresh it makes the event handler auto activate itself without end. If I use Panel.Update it will just draw the second image onto the first. A lot of people recommended that I use the Invalidate method but that has the same infinite loop problem.
Bitmap bitmap = new Bitmap(Resources.Image1);
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (parameter == 0) { bitmap = new Bitmap(Resources.Image1); }
if (parameter >= 2) { bitmap = new Bitmap(Resources.Image2); }
e.Graphics.DrawImage(bitmap, 60, 10);
panel1.Refresh();
}
panel1.Refresh();
Causes the paint event to trigger. I put the refresh code where the parameter value is changed.
Related
I have a panel's Paint event which draws a rectangle around the panel to highlight it.
Paint event-
private void deal_details_panel_Paint(object sender, PaintEventArgs e)
{
int thickness = 2;//it's up to you
int halfThickness = thickness / 2;
using (Pen p = new Pen(Color.GreenYellow, thickness))
{
e.Graphics.DrawRectangle(p, new Rectangle(halfThickness,
halfThickness,
deal_details_panel.ClientSize.Width - thickness,
deal_details_panel.ClientSize.Height - thickness));
}
}
Initially i dont want this rectangle to be shown, I have a button on which if i hover, only then i want to show this rectangle and on MouseLeave event i want to make it invisible again.
Button_Hover-
private void add_MouseHover(object sender, EventArgs e)
{
deal_details_panel.Invalidate();
}
I saw some answers which were to use Invalidate(), but, Invalidate() is not working for me.
Use Refresh() instead of Invalidate().
The Invalidate() method does not force repaint, it simply marks the control as "need repainting", leaving it up to the OS to repaint when needed.
The Refresh() method will force repainting immediately.
For more information, read Whats the difference between Control.Invalidate, Control.Update and Control.Refresh? blog post, where it clearly states:
Control.Invalidate( ) / Control.Invalidate(bool) / Control.Invalidate(Rectangle) / Control.Invalidate(Rectangle, bool) / Control.Invalidate(Region) / Control.Invalidate(Region, bool)
....
The important thing to note here is that these functions only “invalidate” or “dirty” the client area by adding it to the current update region of the window of the control. This invalidated region, along with all other areas in the update region, is marked for painting when the next WM_PAINT message is received. As a result you may not see your control refreshing (and showing the invalidation) immediately (or synchronously).
Update
In order for the panel to show the YellowGreen border only when the mouse is hovering over the button, you need to change your code a little bit.
First, add a bool field to your form, to indicate whether to draw the border or not, initialized to false:
private bool _drawBorder = false;
Then, change the paint method to take that value into consideration:
private void deal_details_panel_Paint(object sender, PaintEventArgs e)
{
if(_drawBorder)
{
int thickness = 2;//it's up to you
int halfThickness = thickness / 2;
using (Pen p = new Pen(Color.GreenYellow, thickness))
{
e.Graphics.DrawRectangle(p, new Rectangle(halfThickness,
halfThickness,
deal_details_panel.ClientSize.Width - thickness,
deal_details_panel.ClientSize.Height - thickness));
}
}
}
Finally, don't use the mouse_Hover event on the button, use MouseEnter and MouseLeave instead.
This is for two reasons - 1: Mouse_Hover have a built in delay and 2: using Mouse_Hover you don't get an indication when the mouse stops hovering over the button.
In the MouseEnter and MouseLeave event handlers, all you need to do is update the value of _drawBorder and call deal_details_panel.Refresh():
private void add_MouseEnter(object sender, EventArgs e)
{
_drawBorder = true;
deal_details_panel.Refresh();
}
private void add_MouseLeave(object sender, EventArgs e)
{
_drawBorder = false;
deal_details_panel.Refresh();
}
Zohar pointed correct solution but I would add a suggestion to change:
using (Pen p = new Pen(Color.GreenYellow, thickness))
to:
using (Pen p = new Pen(Color.Transparent, thickness))
This will set correct state from the start so no need to repaint.
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...
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);
}
}
}
I am putting together an application that uses more than one picturebox on the GUI. I have written custom events for each picturebox that are called on paint events.
Unfortunately, when the application runs, precisely one picturebox displays properly. The others simply display as white boxes. Whichever opicturebox happens to be added first in form.designer is the picturebox that displays properly, for the others their paint events are never triggered. I have tried to get around this by calling various combinations of picturebox.refresh(), picturebox.update() and picturebox.invalidate() in the form constructor, to no avail.
Interestingly, when I associate a button press event with picturebox.refresh() and picturebox.update() and press the button once the program is running, the pictureboxes start behaving as normal - the paint event is called and the image updates.
Does anyone have any ideas how I can get all of the pictureboxes to display properly on initialisation?
Code snippets that may be useful:
Paint code for the pictureboxes:
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
paintPictureBox(e, PictureBox1, currentImage[0]);
}
private void paintPictureBox(PaintEventArgs e, PictureBox picBox,ImageObject c)
{
try
{
Font myFont = new Font("Arial", 14);
//Get bitmap
Bitmap imageToDisplay = new Bitmap(Bitmap.FromFile(c.ImageFile));
//resize smallest dimension to 200
if (imageToDisplay.Height > imageToDisplay.Width)
{
imageToDisplay = new Bitmap(Bitmap.FromFile(c.ImageFile), 200, 200 * imageToDisplay.Height / imageToDisplay.Width);
}
else
{
imageToDisplay = new Bitmap(Bitmap.FromFile(c.ImageFile), 200 * imageToDisplay.Width / imageToDisplay.Height, 200);
}
//crop anything outside of 200x200
imageToDisplay = imageToDisplay.Clone(new Rectangle(Math.Max((imageToDisplay.Width - 200) / 2, 0), Math.Max((imageToDisplay.Height - 200) / 2, 0), 200, 200), imageToDisplay.PixelFormat);
//now draw it
picBox.Image = imageToDisplay;
//add the name
e.Graphics.DrawString(c.Name, myFont, Brushes.Maroon, new Point(2, 200));
}
catch (Exception E)
{
MessageBox.Show("Could not display image successfully\n" + E.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Code from form.designer:
//This one displays correctly on initialisation
this.Controls.Add(this.PictureBox0);
//These two do not
this.Controls.Add(this.PictureBox1);
this.Controls.Add(this.PictureBox2);
((System.ComponentModel.ISupportInitialize)(this.PictureBox0)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.PictureBox1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.PictureBox2)).EndInit();
Code for button press that makes pictureboxes display correctly (after initialisation):
private void Button_Click(object sender, EventArgs e)
{
//Refresh the picture boxes
foreach (PictureBox pb in this.Controls.OfType<PictureBox>())
{
pb.Refresh();
pb.Update();
}
}
The Paint event is raised every time your window needs to be repainted. For example, if another window is being dragged over the top of yours, the Paint event will be raised several times per second. Hence it is important that your paintPictureBox function does nothing but paint.
The problem is that paintPictureBox is continually reinitialising the bitmap but not actually drawing it. Additionally, you are reading from the disk and creating several bitmaps every time you paint. This is slow and uses gobs of memory.
Instead, you should initialise each picture box's Image property once, when the form loads. Each picture box will then take care of drawing its own bitmap. Your paint event should do nothing but draw the picture name.
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.