Click to See Complete Forum and Search --> : [RESOLVED] Trouble with DrawLine
Fedge
Apr 6th, 2007, 06:06 PM
I'm trying to draw a line in a picturebox on my form. Using System.Drawing, I would think this to be really easy but for the life of me I can't get the line to actually appear. When I run the code below, absolutely nothing happens. The form opens, I click button1, and get nothing.
There are no compile errors, and no runtime errors. Why won't the line show up on the form?
The code I'm using to do this is:
using System;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
private Point start = Point.Empty;
private Point end = Point.Empty;
private Random rand = new Random();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
Graphics g;
g = this.pictureBox1.CreateGraphics();
Pen aPen = new Pen(Brushes.Black, 2);
g.DrawLine(aPen, 0, 0, 10, 10);
this.pictureBox1.Refresh();
this.Refresh();
}
}
}
Rauland
Apr 7th, 2007, 11:31 AM
Not sure whether this will work,...
Try,........
this.Invalidate
Instead of
this.refresh.
chemicalNova
Apr 7th, 2007, 12:41 PM
Its been a while since I've done anything similar, but I'm fairly certain refreshing the control erases the drawn lines.
Try it without refresh.
chem
Fedge
Apr 7th, 2007, 01:08 PM
Thanks guys. You're both correct. I removed
this.pictureBox1.Refresh();
this.Refresh();
and everything works perfect. It works with or without this.Invalidate(), for what that's worth. I had a feeling it was something really dumb on my part. Thanks for the help.
jmcilhinney
Apr 7th, 2007, 07:31 PM
That code is actually no good. Drawing anything on a control with GDI+ anywhere but in that control's Paint event handler is wrong. The reason that your original code wasn't working was that by calling Refresh you were forcing the control to repaint, which was erasing your drawing. Now that you've removed that call your drawing will stay, but only until the control is repainted.
Try minimising and restoring your form, or dragging another window over it, and you'll see that your drawing just disappears. That's because the form is repainted and your drawing isn't done again so it's lost. ALL drawing on controls with GDI+ MUST be done in that control's Paint event handler. What you do is set the values of control variables that describe what to draw, then call the control's Refresh method or its Invalidate and Update methods. That will force the control to repaint, which raises its Paint event. You handle that event, retrieve the values of your control variables and perfrom the drawing that they describe.
Check out the #1 FAQ question here (http://www.bobpowell.net/faqmain.htm).
wossname
Apr 8th, 2007, 07:37 AM
The best plan is to setup a bitmap and draw everything to that when required, then just draw that bitmap to the control in the paint event. Easy and persistent.
Fedge
Apr 8th, 2007, 12:42 PM
jmcilhinney, thanks for your reply.
I hadn't noticed that until you pointed it out, but the repainting is an issue now. I checked out the FAQ question, and while the code does seem to work, I can't quite get it to suit my purpose.
I have a TableLayoutPanel that contains a whole bunch of picturboxes, each containing an image. On a button click, I need to draw lines on top of on one or more of the picturesboxes. The coordinates for the lines vary and are somewhat dynamic, so I (think) I need someway to pass the paint event variable coordinates. The picturboxes are added to the table at runtime.
After reading the FAQ, it seems I need a paint event for each box?? Do I have to have 100 different paint event functions?
jmcilhinney
Apr 8th, 2007, 07:13 PM
You need a Paint event handler for every control you want to draw on. If you have 100 PictureBoxes that you want to draw on then you need a Paint event handler for each one. That doesn't necessarily mean that you need 100 different event handlers though. If the process is the same for all of them then you can simply create one event handler and have it handle the Paint event for all the controls. The easiest way to distinguish what you need to draw is with a Dictionary. You create a Dictionary keyed on the control where the values are the drawing control data. In the Paint event handler you use the sender to get the value from the Dictionary for that control. Here's a very simple example of drawing a line on three PictureBoxes. Create a new project and add three PictureBoxes to the form. Now replace the form's code with the following:Public Class Form1
'A structure that represents a line with a start point and an end point.
Private Structure Line
Public Start As Point
Public [End] As Point
Public Sub New(ByVal start As Point, ByVal [end] As Point)
Me.Start = start
Me.End = [end]
End Sub
End Structure
'Line objects keyed by the PictureBox they will be drawn on.
Private linesByPictureBox As New Dictionary(Of PictureBox, Line)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Add three lines to the Dictionary keyed by the PictureBox they will be drawn on.
Me.linesByPictureBox.Add(Me.PictureBox1, _
New Line(New Point(10, 5), _
New Point(15, 30)))
Me.linesByPictureBox.Add(Me.PictureBox2, _
New Line(New Point(20, 10), _
New Point(5, 25)))
Me.linesByPictureBox.Add(Me.PictureBox3, _
New Line(New Point(0, 15), _
New Point(35, 10)))
End Sub
Private Sub PictureBox_Paint(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox3.Paint, _
PictureBox2.Paint, _
PictureBox1.Paint
'Get the PictureBox that raised this Paint event.
Dim picture As PictureBox = TryCast(sender, PictureBox)
If picture IsNot Nothing AndAlso Me.linesByPictureBox.ContainsKey(picture) Then
'Get the line for this PictureBox.
Dim drawing As Line = Me.linesByPictureBox(picture)
'Draw the line on the PictureBox.
e.Graphics.DrawLine(Pens.Black, drawing.Start, drawing.End)
End If
End Sub
End ClassNow run the project and you'll see that the appropriate line gets drawn on each PictureBox. You can change the values of the lines at run time if you want and then when you refresh the controls the old lines will disappear and the new ones will be drawn.
Check out my simple drawing program http://www.vbforums.com/showthread.php?t=426684 too.
Edit: Ah b*gger. I got to this thread via my UserCP and I'd forgotten it was in the C# forum. Anyway, hopefully you get the idea and you can do basically the same thing in C#:
1. Create a Dictionary keyed on your controls.
2. Store the drawing data, possibly in a custom type, in the Dictionary values.
3. In the common Paint event handler use the sender to get the drawing data for that control.
4. Draw the shapes represented by that data.
Fedge
Apr 11th, 2007, 02:35 PM
jmcilhinney, I adapted your code sample to my needs as best I could, and have it working... sorta.
The problem I'm having is adding the lines to the dictionary. The pictureboxes are created in a big 3D array at runtime and then added to the form like this:
PictureBox[,,] picArray = new PictureBox[2, 9, 9];
...
for (i,j,k, etc..)
{ { {
picArray[i, j, k] = new PictureBox();
picArray[i, j, k].Image = imgSB0;
picArray[i, j, k].SizeMode = PictureBoxSizeMode.AutoSize;
picArray[i, j, k].Name = "picBox" + i + j + k;
picArray[i, j, k].Paint += new PaintEventHandler(PictureBox_Paint);
table1.Controls.Add(picArray[i, j,k], i + 1, j);
} } }
Then I add the picBox to to the dictionary like this:
Dictionary<PictureBox, Line> dicPictureLines = new Dictionary<PictureBox, Line>();
...
this.dicPictureLines.Add(picArray[x, y, z], new Line(new Point(10, 5), new Point(15, 30)));
And your PaintEvent handler (translated to C#) looks like this:
private void PictureBox_Paint(object sender, PaintEventArgs e)
{
PictureBox picture = sender as PictureBox;
if (picture != null && dicPictureLines.ContainsKey(picture))
{
Line drawing = this.dicPictureLines[picture];
e.Graphics.DrawLine(new Pen(Brushes.Black, 2), drawing.Start, drawing.End);
}
}
So, the problem is that when this.dicPictureLines.Add(picArray[x, y, z],new Line(...) ); is called, nothing is drawn. I think this has something to do with the way the pic boxes in the array are actually stored in the dictionary, but I'm still trying to figure that out.
The above code works on picboxes that I created at design time, and are not in an array, so the functionality is there. I just need to figure out how to make it work with this picbox array. Perhaps you could shed some light on this for me, and thanks for all your help so far.
Fedge
Apr 11th, 2007, 03:12 PM
I think I just solved my own problem. I started from scratch on a fresh project to make this code work, and I managed to do so. Trying to write in this paint/dictionary code in my giant program before attempting small scale was no doubt the source of my problems.
Anyway, here's the small-scale version that works. I kept the array 2D for now, but you get the idea. Three picboxes are created at design time and one of them is created at runtime (picArray[0,3]).
public partial class Form1 : Form
{
Dictionary<PictureBox,Line> dicPictureLines = new Dictionary<PictureBox,Line>();
PictureBox[,] picArray = new PictureBox[2,4];
private struct Line
{
public Point Start;
public Point End;
public Line(Point start, Point end)
{
this.Start = start;
this.End = end;
}
}
public Form1()
{
InitializeComponent();
picArray[0,0] = pictureBox1;
picArray[0,1] = pictureBox2;
picArray[0,2] = pictureBox3;
picArray[0, 3] = new PictureBox();
picArray[0, 3].BorderStyle = BorderStyle.FixedSingle;
this.Controls.Add(picArray[0, 3]);
picArray[0, 0].Paint += new PaintEventHandler(PictureBox_Paint);
picArray[0, 1].Paint += new PaintEventHandler(PictureBox_Paint);
picArray[0, 2].Paint += new PaintEventHandler(PictureBox_Paint);
picArray[0, 3].Paint += new PaintEventHandler(PictureBox_Paint);
}
private void PictureBox_Paint(object sender, PaintEventArgs e)
{
PictureBox picture = sender as PictureBox;
if (picture != null && dicPictureLines.ContainsKey(picture))
{
Line drawing = this.dicPictureLines[picture];
e.Graphics.DrawLine(new Pen(Brushes.Black, 2), drawing.Start, drawing.End);
}
}
private void button1_Click(object sender, EventArgs e)
{
this.dicPictureLines.Add(picArray[0, 0], new Line(new Point(10, 5), new Point(15, 30)));
this.dicPictureLines.Add(picArray[0, 1], new Line(new Point(20, 10), new Point(5, 25)));
this.dicPictureLines.Add(picArray[0, 2], new Line(new Point(0, 15), new Point(35, 10)));
this.dicPictureLines.Add(picArray[0, 3], new Line(new Point(0, 15), new Point(35, 10)));
this.Refresh();
}
}
Thanks again, jmcilhinney, for your dictionary code. It worked like a charm, once I stopped being a moron :bigyello:
Fedge
Apr 11th, 2007, 04:06 PM
I just realized this code only works for drawing one line per picbox. I need to be able to draw multiple graphics to each picbox. Like... 2 lines and a piece of text or 4 lines and 3 pieces of text. The above code doesn't work that way.
Calling:
this.dicPictureLines.Add(picArray[0, 3], new Line(new Point(0, 15), new Point(35, 10)))
this.dicPictureLines.Add(picArray[0, 3], new Line(new Point(0, 0), new Point(10, 10)))
throws and error because the dictionary already contains a value for that particular picturebox.
I tried:
this.dicPictureLines.Add(picArray[0, 3], new Line(new Point(0, 15), new Point(35, 10)))
this.dicPictureLines.Remove(picArray[0, 3]);
this.dicPictureLines.Add(picArray[0, 3], new Line(new Point(0, 0), new Point(10, 10)))
but it removes the first line before adding the second line.
Is there an easy fix for adding multiple graphics?
jmcilhinney
Apr 11th, 2007, 06:22 PM
In my example I was drawing one line per PictureBox so the values in my Dictionary were Line objects. If you wanted to draw multiple lines per PictureBox then the values in your Dictionary could be List<Line> objects. If you want to be able to draw multiple lines and multiple text strings then you should declare your own type that has properties containing a List<Line> and a List<string> and the then the values in the Dictionary should be Lists of that type.
Fedge
Apr 11th, 2007, 07:44 PM
jmcilhinney, could you elaborate on declaring a type for List<line> and List<string>? I modified the dictionary to accept multiple lines using List<Line> but I'm a little unawares as to how to create another type.
I presume some sort of class like GraphicObject that has a list of Lines and Strings, but I think I need some direction.
jmcilhinney
Apr 11th, 2007, 08:01 PM
If all you want are lines and string:public class Line
{
private Point _start;
private Point _end;
public Point Start
{
get { return _start; }
set { _start = value; }
}
public Point End
{
get { return _end; }
set { _end = value; }
}
public Line(Point start, Point end)
{
this._start = start;
this._end = end;
}
}
public class DrawingObjectCollection
{
private List<Line> _lines;
public List<Line> Lines
{
get
{
if (this._lines == null)
{
this._lines = new List<Line>();
}
return _lines;
}
set { _lines = value; }
}
private List<string> _strings;
public List<string> Strings
{
get
{
if (this._strings == null)
{
this._strings = new List<string>();
}
return _strings;
}
set { _strings = value; }
}
}Now you Dictionary would be declared:private Dictionary<PictureBox, DrawingObjectCollection> drawingObjectsByPictureBox = new Dictionary<PictureBox, DrawingObjectCollection>();In the Paint event handler you cast the sender as a PictureBox and get the corresponding DrawingObjectCollection from the Dictionary. You'd then loop through its Lines and Strings collections and draw each one. If you want to be able to draw other types of objects too then you'd need to add more properties to the DrawingObjectCollection class. For instance, if you wanted to draw rectangles too you'd add another property of type List<Rectangle>. The Framework already contains a Rectangle type so you don't need to declare that. If you wanted to draw circles then you'd have to declare a Circle type and add a List<Circle> property to the DrawingObjectCollection class, etc., etc.
Fedge
Apr 11th, 2007, 09:39 PM
Thanks for the class, I really do appreciate it. I was able to make your code work in my application.
While it does allow me to draw multiple lines/strings, etc, I can only draw them at the same time. What I mean to say is, on a button click, x number of graphics are added to the picbox all at the same time.
Is it possible to add more graphics at a later time? For example, on one button click, 2 lines and some text is added. Then, on another button click, 1 more line and more text is added.
I'm not sure if this is possible. It seems like I'm starting to push the limits of using a dictionary to draw graphics.
jmcilhinney
Apr 11th, 2007, 10:02 PM
There are no limits. The whole point of collections is that you can add and remove items at will. If you have a DrawingObjectCollection that corresponds to a particular PictureBox you can add a new Line to its Lines collection or a new string to its Strings collection at any time. Then when you repaint that PictureBox the new Line(s) and/or new string(s) will be drawn along with all the others. You can make this all as complex as you like.
Fedge
Apr 11th, 2007, 10:09 PM
Well, jmcilhinney, I can't thank you enough for your help. I'd offer to buy you a beer sometime, but I just realized you're located in Australia. If you ever make it to the states, let me know and I'll get you a brew.
jmcilhinney
Apr 11th, 2007, 10:10 PM
Note that the example I provided is still relatively simple. You still have to provide the Pens, Brushes, Fonts and text locations elsewhere. Instead of your DrawObjectCollection class containing collections of simple Line and string objects you could make it contain collections of objects that contain all the required data to draw something, like the Pen, Font and location for a string or the Bruch for a Line. Like I said, you can extend this to whatever level of complexity you like.
Fedge
Apr 11th, 2007, 10:33 PM
Yeah, the other code was already in place (pens, brushes, etc.) so DrawingObjectCollection dropped in pretty easy.
You suggestion for extending the class to contain everything sounds like a good idea. If you hadn't guessed by now, the specifics on the text location, pen, font, and whatnot is extremely complex, and very dynamic, so I've been struggling with allowing the application to remain so flexible.
At the moment, I have everything working great, so I'll save re-working the class for another time (maybe when I feel like a little programming pain :bigyello:).
Thanks again, JM.
vbforums.com
Copyright Internet.com Inc., All Rights Reserved.