I am trying to create this simple application in c#: when the user double clicks on specific location in the form, a little circle will be drawn. By one click, if the current location is marked by a circle - the circle will be removed.
I am trying to do this by simply register the MouseDoubleClick and MouseClick events, and to draw the circle from a .bmp file the following way:
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
Graphics g = this.CreateGraphics();
Bitmap myImage = (Bitmap)Bitmap.FromFile("Circle.bmp");
g.DrawImage(myImage, e.X, e.Y);
}
My problem is that I dont know how to make the circle unvisible when the user clicks its location: I know how to check if the selected location contains a circle (by managing a list of all the locations containig circles...), but I dont know how exactly to delete it.
Another question: should I call the method this.CreateGraphics() everytime the user double-clicks a location, as I wrote in my code snippet, or should I call it once on initialization?
My personal preference is to put my images in instances of the Picturebox class. Reason being, I can simply call each Picturebox's Hide() function (or set 'Visible` to false).
What you're doing is drawing directly onto the window's client area, which technically isn't wrong but normally should be done in the form's Paint handler. If at some point you decide you don't want your circle to be visible anymore, you can call the form's Invalidate() method which triggers the Paint event. There, you explicitly do not draw your circle, and so to the user, the circle disappears.
The nice thing about a Picturebox is that it's persistent - you put your image into it and optionally draw on that image, but you only need to draw once. If you use the Paint handler technique, your drawing code gets called each time the form needs to redraw itself.
Edit:
Here's some code that illustrates my Paint handler information:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(); // clear any and all circles being drawn
if (CircleIsVisible)
{
e.Graphics.DrawEllipse( ... ); // OR, DrawImage( ) as in your example
}
}
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
CircleIsVisible = true;
Invalidate(); // triggers Paint event
}
If you're drawing bitmaps, I would load the bitmap once and store it as a class variable. This way you don't need to hit the hard drive each time you want to draw. Dispose of the bitmap when you dispose of your class (in this case, your window).
I thinks you should clear all of the image you draw before your next double click.
Such as Graphics.Clear().
On the other hand, you should not to create Graphics object or dispose it every time.
If you have simple background color you could use Graphics.DrawEllipse to draw Circles and then just change circle color to the background color. Also you need to have a Collection of all circles you draw so you can access any circle that you've drawn.
Related
I know there are many questions on this, but I just can't get it to work. I have this event:
private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(selectionBrush, 1), selectionRectangle);
}
It works as it should. Draws a colored rectangle over the image I put inside picture box. But after I draw that rectangle, I want to erase it. I basically use it as selection area for cropping the image. Is there a way that I can erase the rectangle that I have drawn? Thanks.
Here is where I want to erase the rectangle:
private void button1_Click(object sender, EventArgs e)
{
//here are some examples of what I have tried
pictureBox1.Invalidate();
Invalidate();
}
Your Paint routine has a call to DrawRectangle. Then you invalidate the rectangle. At that point, your paint routine will get called again, and, if your code to draw the rectangle is still there, it will get redrawn. You need to make it so the DrawRectangle call no longer gets called once you don't need it any more (with an if statement or something).
You also probably want to inflate the size of the invalidation rectangle by one pixel all around to make sure you don't leave any bread crumbs behind
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..
When the form is loading shouldn't the CreateGraphics() return a graphics object?
I mean, on the Form1_Load event can I write for example the following?
Graphics x;
private void Form1_Load(object sender, EventArgs e)
{
x = this.CreateGraphics();
}
If not, then WHY?
I thought that when you create a new form, the constructor initiates the all object of the form. So why not also the graphics object?
I'm asking that, because when I'm trying to draw - on form_load, it doesn't show me what I draw.
The main reason is: I want to create a game, which have a board - so when the user click on the new game - first - I'm initiating the board and drawing it.
And in the onPaint event I just want to draw the current state of the board. So I thought the the initial state of the board should be draw on the formLoad event.
You should not use the Graphics object in this manner; you should enclose each usage of it in a using block, or otherwise make sure you dispose of it after each set of drawing operations. Your code here will leave an unnecessary Graphics object hanging around.
Brief example:
private void MyonPaintOverload()
{
using(Graphics x = this.CreateGraphics())
{
// draw here...
}
}
Also, drawing on Form_Load() won't work, because the window is not actually visible at that point; there's nothing to draw on, basically.
Yes, you generally need to redraw the whole thing each cycle - because something as simple as another window being dragged across your window will 'wipe out' your drawing, and when it's invalidated by the other window being moved away, you need to redraw everything that you manually drew.
I owned complex drawing code with GDI+ which draws something like a chart on a user control.
If the user clicks with control pressed, a vertical marker line in dash style should be shown.
Now I look for a way to extend the drawing code without touching the complex drawing code.
I created a marker class which attaches to the mouse-up event of the user control.
In the eventhandler a check against (ModifierKeys == Keys.Control) is done.
If the user holds the control key and click with the left mouse button the draw method of the marker class is called with the Graphics object of the usercontrol as a parameter.
The current behaviour is that for each click a new line is drawn, but the line should be deleted and a new one should be drawn.
How can i erase the drawed line?
Do I have to redraw the comlete content of the user control?
You cannot delete a drawn line because there is no way to restore the underlying graphics.
What you could do is either:
Redraw the entire graph (without the line)
or
Stack a second transparent usercontrol on top and use that to display the line. Removing and drawing it when needed.
The answer here is clearly yes. With GDI+ you just draw directly on a bitmap buffer, so if you want to undo a previous drawing operation you can do one of those things (depending on the complexity of the issue and performance):
restore the bytes that have been changed on the bitmap buffer
reload a previous state of the drawing bitmap
A simple solution would be to have 2 bitmaps (something like that is usually called double buffering). One that is shown currently (and contains the final state) and one that is used for preview only. The preview one is always a copy of the first one - just with current modifications.
So basic algorithm for this simple implementation:
start with two bitmaps (blank but identical sizes) [named A and B]
if the user draws a line always make a copy of the bitmap A in B and draw on B - show B
if the user finishes the line then make a copy of B in A and again - show B
So always show the preview bitmap, which is just a modified bitmap of the original one.
Here is an example programming code in C# (assuming all events are connected and the preview Bitmap, B, is the picture box itself (which is just named pictureBox1 here):
Bitmap bmp;
bool isDrawing;
Point previous;
void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
isDrawing = true;
previous = e.Location;
}
void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
isDrawing = false;
}
void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isDrawing)
{
using (Graphics g = Graphics.FromImage(bmp))
{
double wf = (double)bmp.Width / (double)pictureBox1.Width;
double hf = (double)bmp.Height / (double)pictureBox1.Height;
g.ScaleTransform((float)wf, (float)hf);
g.DrawLine(Pens.Black, e.Location, previous);
}
pictureBox1.Refresh();
previous = e.Location;
}
}
This code will do everything to display drawing a straight line from a point to another by simply pressing the left mouse button.
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.