C# : Remove Paint Event from PictureBox - c#

Sorry for my bad English.
I have a picturebox where I draw 100000 shapes (but there may be more).
The drawing is made in the Paint Handler of the picturebox.
The problem is : When I Resize the form (where the picturebox is), use the scrollbar of the panel which contain it, come from another application, ... the paint handler is called...
But the paint process takes quite some time and the user must wait until the paint have finished...
I tried the folowing :
Create a bitmap where i draw the shapes
In the paint handler, i copy the bitmap in the picturebox
NB : The size and the content of the picturebox can change, so the bitmap must also change.
The creation of the bitmap + restoration of the bitmap make the application slower than before :
Bitmap bmp = new Bitmap(picturebox.Width, picturebox.Height);
// draw in Graphics.FromImage(bmp);
picturebox.Invalidate();
bmp.Dispose();
I also tried with a boolean flag : canRedraw.
I set it true when the content of the picturebox change and then I call picturebox.Invalidate(). In the paint handler, I check if (canRedraw) and if so, I redraw the content (and canRedraw = false), else I make nothing.
But with this last solution, when I make something with the form, my picturebox is cleared...
Do you have any idea of how I can make this :
If you are a method that change the content of the picturebox then you can redraw the picturebox, else you leave the visual content of the picturebox unchanged.
Can you help me ?
Thank you very much :)

If you're not using any other functionality of the PictureBox, try replacing it with a UserControl of your own. Then take the following steps in your UserControl:
Set DoubleBuffered property of the control to True.
Always check e.ClipRectangle property to get the area that needs to be redrawn. Then loop through the collection of your shapes and for each shape, try to figure our whether it intersects with the ClipRectangle. I don't know what kind of shapes you have, but there are fairly fast implementations available for most shapes, including polygon, that can check whether two polygons intersect or not. A good article about polygons intersection is available in this article, including c# code. (Note that if your shapes are rectangles, circles or triangles, the intersection problem becomes much easier and faster to compute)
Paint a shape only if it intersects with ClipRectangle.

Besides streamlining the Paint as dotNet suggest the other way to do it is pretty much what you tried, but you need to do it right:
Yes, do draw into a Bitmap but not in the Paint event, which will be called unnecessarily and then still take too much time! Instead draw only when you know that your data have changed and need to be redrawn!
You didn't tell us just what you draw but the drawing should be done like this:
void drawStuff()
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
using (Graphics G = Graphics.FromImage(bmp) )
{
// do all your drawing stuff here!!
}
pictureBox.Image = bmp;
}
Call this function whenever you want the data to be drawn again!
Now you can leave the Paint event empty, as the Image if buffered by the System and you can still use the PictureBox.Zoom or Image.Save..

Related

Clear drawn graphics on a picturebox without clearing original picture

In a geographical software written in C#, a PictureBox is used to show GIS map that is saved as a png file in a temporary directory. There is some geometric shapes we need to be drawn on map. We used System.Drawing methods to perform this action.
Sometimes we need to change some properties these shapes or delete them, we need to remove the shapes without making beneath them black. Drawing them again with Color.Transparent obviously doesn't work, using Graphics#Clear(Color.Transparent) doesn't work too for the same reason.
We even tried using another picture box with transparent background that is used only for purpose of drawing shapes on; so that when we use Graphics#Clear(Color.Transparent) map container remains untouched. Sounded like a perfect idea at first, but because i don't know how and why it makes map container PictureBox invisible and map viewer panel is totally black, this idea failed too.
MapViewerForm
|-- Toolbar
|-- StatusBar
|-- MapViewer Panel (Provides scrollbars)
|-- MapContainer Pictutebox
|-- Shapes drawing canvas PictureBbox (The same size and location as map container, only difference is z-order)
I prefer to use the two PictureBoxes and making 'layers' idea, i think it's less unprofessional than the other idea (I am actually a java developer and this is a C# project!), I think there should be something like java's JLayeredPane in C# to adjust z-order of those two picture boxes and omit black screen bug, But if there is a solution to draw shapes on map container itself and then clear them without losing portions of maps lying behind them i'd appreciate that answer too.
P.S: If we load map picture from file and store it in a Bitmap or Image private field and when we need to clear drawings, load image from that field with a piece of code like picMapArea.Image = MapViewer.getInstance().getMapImage(); (Note: MapViewer is a singleton class) the painted shapes will be gone but it's obviously not anything like a "good idea" because of poor performance and lagging.
Thanks in advance.
Simply draw the shapes in an event handler for the picturebox Paint event.
To restore the view, all you have to do is call the picturebox Invalidate() method, so it repaints the Image, and not draw anything in your Paint event handler.
Just use an additional Bitmap:
Bitmap original = LoadBitmap(...);
Bitmap copy = new Bitmap(original);
Graphics graph = Graphics.FromImage(copy);
// draw some extra
PictureBox1.Image = copy;

c#:WindowsForms - performance optimization when redrawing bitmap

In my program I have a picture box, containing a bitmap.(300x300 35kB .PNG file)
If 2 variables(x/z coord) are changed, I draw a new circle every second to the new position accordingly - a timer runs in the background, invoking this method.
void DrawEllipse()
{
// Retrieve the image.
bChamber = new Bitmap(global::Project.Properties.Resources.driveChamber1);
gChamber = Graphics.FromImage(bChamber);
gChamber.FillEllipse(brushChamber, VirtualViewX(), VirtualViewY(), 10, 10);
pictureBoxDriveView.Image = bChamber;
}
Now I'm looking for ways to optimize the performance. Redrawing the pic every 0.2s e.g. slows the program so much, I cant do anything else.
But ultimately I need a more fluent movement of the circle, you can Imagine how it laggs with the 1000ms refresh rate.
Is there a better way to do this, then loading the whole bitmap every time?
Use the Controls the way they were intended.
do not redraw the Bitmap yourself.
just load it 1x in the Picturebox.
handle the Paint event of the picturebox to draw the ellipse
invalidate the Picturebox whenever your coords change.
Draw the circle ONE time in a control (PictureBox)
Put the control across the 300x300 picture box.
When, and only when, the variables change, update the location of the picturebox with the circle.
This way you prevent drawing too many times.
Try setting the DoubleBuffered property of the form to true. This generally results in improved performance.
Also, you should put this
// Retrieve the image.
bChamber = new Bitmap(global::Project.Properties.Resources.driveChamber1);
In the class constructor.
Try this, it does not load the image from disk every time, so it is less expensive.
private Image _origImage = new Bitmap(global::Project.Properties.Resources.driveChamber1);
void DrawEllipse()
{
// Retrieve the image.
Image bChamber = new Bitmap((Image)this._origImage.Clone());
Graphics gChamber = Graphics.FromImage(bChamber);
gChamber.FillEllipse(brushChamber, VirtualViewX(), VirtualViewY(), 10, 10);
pictureBoxDriveView.Image = bChamber;
}

Best practice for OnPaint, Invalidate, Clipping and Regions

I have a User Control with completely custom drawn graphics of many objects which draw themselves (called from OnPaint), with the background being a large bitmap. I have zoom and pan functionality built in, and all the coordinates for the objects which are drawn on the canvas are in bitmap coordinates.
Therefore if my user control is 1000 pixels wide, the bitmap is 1500 pixels wide, and I am zoomed at 200% zoom, then at any given time I would only be looking at 1/3 of the bitmap's width. And an object which has a rectangle starting at point 100,100 on the bitmap, would appear at point 200,200 on the screen provided you were scrolled to the far left.
Basically what I need to do is create an efficient way of redrawing only what needs to be redrawn. For example, if I move an object, I can add the old clip rectangle of that object to a region, and union the new clip rectangle of that object to that same region, then call Invalidate(region) to redraw those two areas.
However doing it this way means I have to constantly convert the objects bitmap coordinates into screen coordinates before supplying them to Invalidate. I have to always assume that the ClipRectangle in PaintEventArgs is in screen coordinates for when other windows invalidate mine.
Is there a way that I can make use of the Region.Transform and Region.Translate capabilities so that I do not need to convert from bitmap to screen coordinates? In a way that it won't interfere with receiving PaintEventArgs in screen coordinates? Should I be using multiple regions or is there a better way to do all this?
Sample code for what I'm doing now:
invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));
SelectedItem.UpdateEndPoint(endPoint);
invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));
this.Invalidate(invalidateRegion);
And in the OnPaint()...
protected override void OnPaint(PaintEventArgs e)
{
invalidateRegion.Union(e.ClipRectangle);
e.Graphics.SetClip(invalidateRegion, CombineMode.Union);
e.Graphics.Clear(SystemColors.AppWorkspace);
e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y);
DrawCanvas(e.Graphics, _ratio);
e.Graphics.ResetTransform();
e.Graphics.ResetClip();
invalidateRegion.MakeEmpty();
}
Since a lot of people are viewing this question I will go ahead and answer it to the best of my current knowledge.
The Graphics class supplied with PaintEventArgs is always hard-clipped by the invalidation request. This is usually done by the operating system, but it can be done by your code.
You can't reset this clip or escape from these clip bounds, but you shouldn't need to. When painting, you generally shouldn't care about how it's being clipped unless you desperately need to maximize performance.
The graphics class uses a stack of containers to apply clipping and transformations. You can extend this stack yourself by using Graphics.BeginContainer and Graphics.EndContainer. Each time you begin a container, any changes you make to the Transform or the Clip are temporary and they are applied after any previous Transform or Clip which was configured before the BeginContainer. So essentially, when you get an OnPaint event it has already been clipped and you are in a new container so you can't see the clip (your Clip region or ClipRect will show as being infinite) and you can't break out of those clip bounds.
When the state of your visual objects change (for example, on mouse or keyboard events or reacting to data changes), it's normally fine to simply call Invalidate() which will repaint the entire control. Windows will call OnPaint during moments of low CPU usage. Each call to Invalidate() usually will not always correspond to an OnPaint event. Invalidate could be called multiple times before the next paint. So if 10 properties in your data model change all at once, you can safely call Invalidate 10 times on each property change and you'll likely only trigger a single OnPaint event.
I've noticed you should be careful with using Update() and Refresh(). These force a synchronous OnPaint immediately. They're useful for drawing during a single threaded operation (updating a progress bar perhaps), but using them at the wrong times could lead to excessive and unnecessary painting.
If you want to use clip rectangles to improve performance while repainting a scene, you need not keep track of an aggregated clip area yourself. Windows will do this for you. Just invalidate a rectangle or a region that requires invalidation and paint as normal. For example, if an object that you are painting is moved, each time you want to invalidate it's old bounds and it's new bounds, so that you repaint the background where it originally was in addition to painting it in its new location. You must also take into account pen stroke sizes, etc.
And as Hans Passant mentioned, always use 32bppPArgb as the bitmap format for high resolution images. Here's a code snippet on how to load an image as "high performance":
public static Bitmap GetHighPerformanceBitmap(Image original)
{
Bitmap bitmap;
bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb);
bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel);
}
return bitmap;
}

Draw a bitmap from a control taller than the screen

I am having quite a few problems saving a C# control to a bitmap file. The control in question is a kind of drawing surface on which the user can write text, draw pictures and paint boxes. The control is resizable and draggable. What happen is, when the control is really big and is not totally visible on the screen, the parts of the control not visible are saved half-drawn on the bitmap. The code used to generate the bitmap is quite simple:
Bitmap bitmap = new Bitmap(myControl.Width, myControl.Height);
myControl.DrawToBitmap(bitmap);
I have tried the following methods to try to have a fully painted bitmap, without any success:
myControl.Invalidate(myControl.ClientRectangle, true);
myControl.Refresh();
myControl.Update();
Application.DoEvents();
I cannot scale the control down to make it fully visible since resolution and image quality are very important for that project. In fact, I am actually trying to scale the image up to increase it's quality. Are there ways I am not aware of generating an image from a control ?
Tank you.
DrawToBitmap has limitations and dont always work as expected. Try instead work with native GDI+
Here is example
Maybe my answer here Capturing a Window that is hidden or minimized can help you?

C# Slow Paint Event: painting large number of objects

I have a PictureBox it serves as a canvas.
A List<RectangleObj> the array size of aprox 8000.
The "RectangleObj" is a simple rectangle class, once you invoke its Draw(Graphic g) method it will draw
the border using
g.DrawRectangle(...) and,
fill the rectangle with an alpha
transparency using
g.FillRectangle(...)
In the application Form.cs, i use the pictureBox1_Paint(...) to loop the array of RectangleObj and invoke the Draw method of that class.
Like this.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < RectList.Count(); i++) //List<RectangleObj> count = 8000
RectList[i].Draw(e.Graphics);
}
Every time the mouse clicks upon a RectangleObj and drags changing its location (mouse move event) the paint event is called. since the array is large in number, the paint event does not have enough time to finish its loop and gets flooded with mouse movements. so, this makes the Paint event slow.
Can somebody advice me how to optimize this procedure.
Have you considered drawing to an in memory bitmap, then blitting that to the screen?
There are a couple of things here:
don't draw while moving. Calculate an outline for the selected elements and XOR only that outline when the mouse moves. Repaint on MouseUp.
don't draw stuff you don't need. You can run an algorithm to detect which rectangles are totally obscured and ignore them.
Make sure you have DoubleBuffered=true for your Picturebox
Work out which rectangles need to be redrawn based on the location of the moving rectangle and its prior location and only redraw those. Clip the painting to only those parts that have been invalidated.

Categories

Resources