I'm trying to improve the performance of my Winform application, and I found that I wasn't disposing graphics objects and instead relying on garbage collection, how can I implement the Graphics.Dispose method into my paint event?
public void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(bigImage, scrollX, scrollY);
e.Graphics.DrawImage(smallImage, peanutHitboxX, peanutHitboxY);
}
The image bigImage is an image bigger than the screen, and constantly moving. I want to dispose the graphics from it but when I change the code to this:
public void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(bigImage, scrollX, scrollY);
e.Graphics.Dispose();
e.Graphics.DrawImage(smallImage, peanutHitboxX, peanutHitboxY);
}
It says that smallImage isn't valid, and moving e.Graphics.Dispose(); to the bottom returns an error in an entirely different file used by winforms
How do I just dispose bigImage without messing up things with smallImage?
Related
I am developing the GUI in C# using picturebox, button, and check box.I need to plot two different plots in parallel using one text file on an image. The image first reads in a picture box. The rest of the program has to be executed in the following way:
Reads text file contains the data points which have to be plotted.
After clicking the button the program execution started with the plotting of the First graphic (rectangular dot), however when the check box is checked the second graphic (continuous dots--path plot) plotting started in parallel with the first graphic.
when the check box is unchecked the second graphic stopped plotting and disappears.(As Both of the graphics style using the same text file).
I need help what to do in this case, should I create the separate thread for check box for this parallel plotting??
please help me where I am mistaken? And pardon my horrible English
From my point of view, the easiest way would be to reload the image into the picture box again and then redraw the first graphics object on it. So, the second one 'disappears'.
An additional thread makes no sense, because drawing must occur on the UI thread only (Windows general rule for GDI+, WinForms, WPF).
Such basic drawing as in your example is very fast.
Edit:
namespace PictureBoxDrawing
{
public partial class Form1 : Form
{
private Bitmap _bmpImage;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_bmpImage = new Bitmap(#"C:\Image.jpg");
InitializePictureBox(_bmpImage);
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
DrawPictureBox(pictureBox1, 10, 10, checkBox1.Checked);
}
private void button1_Click(object sender, EventArgs e)
{
DrawPictureBox(pictureBox1, 10, 10, checkBox1.Checked);
}
private void button2_Click(object sender, EventArgs e)
{
InitializePictureBox(_bmpImage);
}
private void DrawPictureBox(PictureBox pb, int x, int y, bool drawBlue)
{
using (Graphics g = pb.CreateGraphics())
{
g.FillRectangle(Brushes.Red, x,y, 9,9);
if(drawBlue)
g.FillRectangle(Brushes.Blue, x, y, 7, 7);
}
}
private void InitializePictureBox(Bitmap bmp)
{
pictureBox1.Image = bmp;
}
}
}
From your example, here is my simplified suggestion. Load and cache the bitmap in a field for later use. I have added a second button2, which can be used to reload the image into the picture box to demonstrate the behaviour. Because the red rectangle is greater than the blue (9 > 7), it overwrites it when redrawing. So it is not necessary to reload the bitmap, if the position is constant. If the position changes, call InitializePictureBox prior to the DrawPictureBox call.
I have a function that will draw a image to a form.
private void DrawImage()
{
OpenFileDialog openfiledialog = new OpenFileDialog();
if (openfiledialog.ShowDialog() == DialogResult.OK)
{
Bitmap image = (Bitmap)Image.FromFile(openfiledialog.FileName, true);
TextureBrush texturebrush = new TextureBrush(image);
texturebrush.WrapMode = System.Drawing.Drawing2D.WrapMode.Tile;
Graphics formGraphics = this.CreateGraphics();
formGraphics.FillRectangle(texturebrush, new RectangleF(90.0F, 110.0F, 00, 300));
formGraphics.Dispose();
}
}
But i will not draw any image and same code works if i write in button click event.
private void button1_Click(object sender, EventArgs e)
{
//Same code as written in DrawImage()
}
The problem I think is it needs "EventArgs e" in it. I read some where in msdn but not sure.No idea what is the role of EventArgs in drawing image on form.
Is there any way that I can achieve this functionality.
TL;DR
You don't have to. For your case, just ignore them. They may be, however, useful sometimes. Read full answer to understand.
Full answer
When dealing with events, you receive a generic EventArgs object and if you write a custom event, you may want to receive a custom implementation of EventArgs.
To make things simple, consider it a base class that holds event-related data.
So, for instance, if you are implementing a click event, as shown, you will receive two arguments: a source for the event and a holder for the event-related data.
Sometimes this is just useless. As on your sample. You don't have to receive it, nor use it. You may, however, perform some check on the source or the event data but that's not the goal of your code so both are just useless here.
As for me, I prefer to keep my methods apart from events, and call them from the event. I do place every event binding in a "group" on the bottom of the class to keep everything clean and readable. That's just a suggestion, you have to find your very own way of keeping code.
For the sake of this answer, here follows two samples:
1. Useless arguments
This sample just closes a form or window.
public void btn1_Click(object sender, EventArgs e) { Close(); }
2. Usefull arguments
Consider a component with a data grid of some type which fires a SelectedRowsChanged event with the id's (PK) of the selected rows.
// Event declaration. You can, after that, bind it elsewhere.
public event EventHandler<SelectionEventArgs> SelectedRowsChanged;
// This is the local implementation wich will fire the event.
// Here you invoke the event with the selected rows id's.
public void OnSelectedRowsChanged() { if (SelectedRowsChanged != null) CustomSelection(this, new SelectionEventArgs(this.SelectedRows)); }
// This is the custom implementation of the EventArgs to include the
// event-related data (row id)
public class SelectionEventArgs : EventArgs
{
public int[] SelectedRows{ get; private set; }
public SelectionEventArgs(int[] selectedRows) { SelectedRows = selectedRows; }
}
// ... then, somewhere else on your code
this.myControl.SelectedRowsChanged += myControl_SelectedRowsChanged;
public void myControl_SelectedRowsChanged(object sender, SelectionEventArgs e)
{
if (e.SelectedRows.Length > 0) { /* do something */ }
}
For the strong of heart, you may play a bit with lambda too. So, instead of:
this.button1.Click += button1_Click;
public void button1_Click(object sender, EventArgs e) { DoSomething(); }
you may have just this:
this.button1.Click += (s, e) => { DoSomething(); };
As on the first sample, event args are there ((s, e)) but they are just useless for the DoSomething method.
Lambdas are available in C# 5 (.NET 4.5) and above.
Sometimes it's easier to just do this in a form constructor or something alike.
Consider these tips to solve the problem:
If you use this.CreateGraphics for drawing on your form, then your drawing will disappear if the form refershes, for example if you minimize and restore it. You should put drawing logic in Paint event.
When refactoring your method, you should pass a parameter of Graphics type to your method and use it for drawing. Also you should not dispose passed parameter. You should pass e.Graphics in Paint event to your method.
In your method you should dispose your brush. Put it in a using block.
When refactoring your method, you should move the part of code which shows a dialog to out of your method. You should call it just when you need not in Paint event handler.
Preferably Don't use Image.FromFile to load image, it locks the file until the images disposes. Instead use Image.FromStream.
You are using a rectangle with 0 as Width. So if you draw even using current method, you will not see any result because of width of rectangle.
Code
Bitmap image;
private void DrawImage(Bitmap image, Graphics g, Rectangle r)
{
using (var brush = new TextureBrush(image))
{
brush.WrapMode = System.Drawing.Drawing2D.WrapMode.Tile;
g.FillRectangle(brush, r);
}
}
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
using (var s = new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Open))
image = new Bitmap(Image.FromStream(s));
this.Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (image != null)
this.DrawImage(image, e.Graphics, new RectangleF(10, 10, 200, 200));
}
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.
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 have a form with a MenuStrip and a PictureBox. I subscribe to the PictureBox's Paint event with the following code:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
// other code but the above line demonstrates the problem
}
This for some reason causes the MenuStrip to white out--I can't see the text until I hover over it--even though the PictureBox does not overlap the MenuStrip at all. I can put a menuStrip1.Update() at the end of the function above, but that causes other problems.
Instead of directly assigning to the PictureBox while in the event, use the provided PaintEventArgs to do any drawing.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Gray);
}