I am working on a simple windows forms paint application. I am having problem in clearing the panel. The code i am using to draw is
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = Graphics.FromImage(tempDraw);
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
e.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
How to clear the panel?
Are drawing in the paint handler of the Panel instance? If not then calling Invalidate on the panel would do.
But you will probably be persisting the drawing items and so to clear them you would need to delete what has been drawn and then call Invalidate. You could also fill the Panel with a particular color using FillRect but that would be a dirty workaround and not fit your final design.
You should also check out CodeProject.com for examples like this one to give you an idea on what needs to be handled when creating a drawing app like this.
EDIT:
Per the edited answer, you cannot clear the panel with the existing logic. You are painting inside Paint handler of the form which will happen any time it needs to be redrawn. This means that you should change your approach. You need some sort of condition inside the Paint handler which decides whether or not it will paint anything at all. This is where the persistence of drawing objects comes in. If you want to create a drawing program then you will have to handle the mouse Down, Up and Move events over the panel objects and store the data in a points array. (As an example of one type of drawing.) Then in your Paint handler if the Points[] is not empty you draw the points. Otherwise you draw nothing... which ends up in an empty container. Then if you need to clear the drawing you delete the contents of the Points array and call Invalidate on the Panel. That will clear the persisted data and repaint to nothing.
You can use
Panel1.Invalidate();
But there is a problem with this, after you call this function it clears all the graphics from the panel, but it also recalls the function i.e.
private void panel1_Paint(object sender, PaintEventArgs e)
{
//This function is recalled after Panel1.Invalidate();
}
So solution is to make your paint code in some other function
private void MyDrawing()
{
Graphics g = Graphics.FromImage(tempDraw);
// if above line doesn't work you can use the following commented line
//Graphics g = Graphics.Panel1.CreateGraphics();
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
Panel1.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
You'll have to draw over the panel again with whatever base colour you're using eg. white\grey with the Graphics.FillRectangle method:
// Create solid brush.
SolidBrush whiteBrush = new SolidBrush(Color.White);
// Create location and size of rectangle.
// Fill rectangle to screen.
e.Graphics.FillRectangle(whiteBrush, panel.Location.X, panel.Location.Y, panel.Width, panel.Height);
this.Invalidate();
Related
in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks
You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.
if i draw some rectangles in panel , how can i select one of them and delete it. My code here i have write it inside panel_OnClick event :
g = panel1.CreateGraphics();
Pen p = new Pen(Color.Black);
p.Width = 2;
g.DrawRectangle(p, e.X, e.Y, 100, 60);
p.Dispose();
g.Dispose();
Drawing on the panel is like drawing on a piece of paper - they are etched in and are no longer a rectangle, but a collection of pixels. Even though you could draw a rectangle over the one you want to clear using the background color, you won't be "removing the rectangle", you'll just draw a rectangle over the existing one.
Rectangle will have a Region.
You will need to subscribe to one of the following: MouseClick, MouseDown, MouseUp.
// assuming you keep a reference of the rectangle
void OnMouseClick(object sender, MouseEventArgs e) {
if(myRect.Region.IsVisible(e.Location) {
// perform action on myRect ...
// have window Invalidate(myRect)
// Refresh() the invalidated area.
}
}
This snippet assumes that no Rectangles overlap. You can also create a GraphicsPath from the points of the Rectangle and then from that path, I believe you can create a Region that enables the actual lines of the rectangle to be selected.
Update per comment
Region
GraphicsPath
I checked and I didn't see the Region property for Rectangle. So, to create the Region do the following:
var gPath = new GraphicsPath();
gPath.AddRectangle(rectangle);
var region = new Region(gPath);
in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks
You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.
On a blank winform code can be added to show lines that intersect (crosshairs) at the mouse pointer. The problem is that the lines don't show (or are partially hidden) by controls on the form (ie listview, splitcontainer, buttons).
How would I modify the code below to show on-top (bring to front...) of all the controls present on the form?
int lastX = 0;
int lastY = 0;
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Region r = new Region();
r.Union(new Rectangle(0, lastY, this.Width, 1));
r.Union(new Rectangle(lastX, 0, 1, this.Height));
this.Invalidate(r);
this.Update();
Graphics g = Graphics.FromHwnd(this.Handle);
g.DrawLine(Pens.Chocolate, 0, e.Y, this.Width, e.Y);
g.DrawLine(Pens.Chocolate, e.X, 0, e.X, this.Height);
lastX = e.X;
lastY = e.Y;
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
this.Invalidate();
}
You need a transparent window that's on top of all the other controls. The only way to get one is by overlapping the form with another form, made transparent with its TranparencyKey property. You'll find sample code for this in my answer in this thread.
Please just try first sending to back(Control.SendToBack()) the controls on the form (ie listview, splitcontainer, buttons). Put this at the FormLoad event. I have experimented the same nightmare with a Windows MDI application.
Hope that helps,
Enumerate through the desired controls and call the .BringToFront(); function on them.
listBox1.BringToFront();
According to the documentation, the region object should be in world co-ordinates, you're passing in client co-ordinates. Use Control.PointToScreen to map the rectangles' top left coordinate to world space.
I'd also be tempted to defer the drawing to the OnPaint method.
Can I delete the old rectangle which I have drawn and draw a new rectangle?
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
Graphics g = this.panel1.CreateGraphics();
Pen pen = new Pen(Color.Black, 2);
g.DrawRectangle(pen, 100,100, 100, 200);
g.dispose();
}
No, you cannot "delete" something that's already been drawn. You can overwrite it with something else, but drawing with Graphics objects is like painting in real-life: once the paint is dry, you can only paint over it with another colour, you can't "erase" it.
You probably shouldn't be drawing things in response to a MouseClick, either. It's best to only draw things in response to a Paint event. What I would do in this situation is add a Rectangle structure to a list on the MouseClick and then call panel1.Invalidate() to ask it to redraw itself. Then in the Paint event for the panel, do the drawing there.
This will kill two birds with one stone, because you will be able to "erase" thing by simply removing them from the list of stuff to draw.
This is usually done by maintaining a collection of objects you want drawn. The mouse click should update this collection and then tell the window (or the affect region) to refresh. This has the enormous advantage of preserving whatever you've drawn if the window is moved off-screen, hidden behind other windows, minimized, etc.
For a rudimentary solution, create a hierarchy of drawable shape types derived from a common abstract Shape class, and use, e.g., a List for the collection. The base Shape class will have an abstract Draw method that the derived classes override.
For a more industrial-strength solution, look around for 2-D scene graphs.
One can use Graphics.Save() and Graphics.Restore(state) methods for that. For example:
private void SaveRestore2(PaintEventArgs e)
{
// Translate transformation matrix.
e.Graphics.TranslateTransform(100, 0);
// Save translated graphics state.
GraphicsState transState = e.Graphics.Save();
// Reset transformation matrix to identity and fill rectangle.
e.Graphics.ResetTransform();
e.Graphics.FillRectangle(new SolidBrush(Color.Red), 0, 0, 100, 100);
// Restore graphics state to translated state and fill second
// rectangle.
e.Graphics.Restore(transState);
e.Graphics.FillRectangle(new SolidBrush(Color.Blue), 0, 0, 100, 100);
}
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.restore.aspx
Also, depending on the application, you might look at using DrawReversibleFrame. You can change the rectangle location by calling the Offset method.
Instead of calling g.DrawRectangle(pen, 100,100, 100, 200); , maintain the rectangle as a object which will be drawn by the graphics object. Each time you will update this rectangle object with new one and graphics object will draw the new one.
The refresh should clear the old rectangle and graphics will draw the new one.
You can just use VisualBasic PowerPacks, it is included with my version of Visual Studio 2008
Here's a sample code that will draw a rectangle over a TextBox, i.e. I am giving it a custom border
Dim x = TextBox1.Location.X
Dim y = TextBox1.Location.Y
Dim width = TextBox1.Width
Dim height = TextBox1.Height
Dim ShapeContainer1 As New Microsoft.VisualBasic.PowerPacks.ShapeContainer
Me.Controls.Add(ShapeContainer1)
Dim RectangleShape1 As New Microsoft.VisualBasic.PowerPacks.RectangleShape
ShapeContainer1.Shapes.AddRange(New Microsoft.VisualBasic.PowerPacks.Shape() {RectangleShape1})
RectangleShape1.Location = New System.Drawing.Point(x - 1, y - 1)
RectangleShape1.Size = New System.Drawing.Size(width + 1, height + 1)
RectangleShape1.BorderColor = Color.MistyRose
ShapeContainer1.Refresh()
Code is self describing but if you'd have any problem, just leave a message...
I think using DrawReversibleFrame is the right solution.
The first call draw the rectangle, the second call undraw it and so on.
Here is a sample code, a clic on the button will make the rectangle appear/disapper.
Rectangle pRect = new Rectangle(10, 10, 20, 20);
private void rect_Click(object sender, EventArgs e)
{
ControlPaint.DrawReversibleFrame(pRect, this.BackColor, FrameStyle.Thick);
}