How to draw the points( with floating value) over a bitmap picture? - c#

I captured a video and took out a frame of that, converted it to Bitmap and now I can show it on picture box.
I have some float points which is the return values of GoodFeaturesToTrack() function from image class.
Now I want to draw/show those points/marks on different Xi,Yi over my picture;
How is it possible to do it? which command I have to use?

You could use builtin OpenCV functions to render around the feature points found, before you convert your image to a normal bitmap. This is also going to be much faster, as the image class will work with the raw memory rather than issue graphics calls.
Here's an (incomplete) example to illustrate the point. Note: you might need to adjust the calls to the CV signatures declared by your wrapper:
private int maxPointCount = 16;
private CvPoint2D32f[] points = new CvPoint2D32f[maxPointCount];
private CvImage grayImage = new CvImage(size, CvColorDepth.U8, CvChannels.One);
private CvImage eigenValues = new CvImage(size, CvColorDepth.F32, CvChannels.One);
private CvImage tempImage = new CvImage(size, CvColorDepth.F32, CvChannels.One);
public int FeatureRadius { get; set; }
private CvScalar featureColor;
public Color FeatureColor
{
get
{
return Color.FromArgb((byte)featureColor.Value2, (byte)featureColor.Value1, (byte)featureColor.Value0);
}
set
{
featureColor.Value0 = value.B;
featureColor.Value1 = value.G;
featureColor.Value2 = value.R;
}
}
public void Process(CvImage input, CvImage output)
{
CV.ConvertImage(input, grayImage);
CV.GoodFeaturesToTrack(grayImage, eigenValues, tempImage, points, ref maxPointCount, 0.01, 10, IntPtr.Zero, 3, 0, 0.04);
CV.Copy(input, output);
// This draws a circle around the feature points found
for (int i = 0; i < pointCount; i++)
CV.Circle(output, new CvPoint((int)points[i].X, (int)points[i].Y), FeatureRadius, featureColor);
}

Add a handler for the PictureBox.Paint event and do your drawing there. If you need to refresh the drawing call Invalidate() on your PictureBox control to redraw.
void PictureBox_Paint(object sender, PaintEventArgs e) {
// draw points from var pointsList = List<Point>
foreach (Point p in pointsList) {
e.Graphics.DrawEllipse(Pens.Yellow, p.X - 2, p.Y - 2, 4, 4);
}
}

Related

Cannot modify the return value of a collection of rectangles

Here's what I used to solve this problem:
public class Snake {
List<Rectangle> bodyParts = new List<Rectangle>();
}
Snake snk = new Snake();
snk.bodyParts.Add(new Rectangle(760,25,8,8))
//the Exception Occurs here
//snk.bodyParts[0].Y = snk.bodyPArts[0].Y-10;
//Here's my approach
snk.bodyParts[0] = new Rectangle(bodyParts[0].X,bodyParts[0].Y-10,8,8);
Exception: Cannot modify the return value of 'System.Collections.Generic.List.this[int]'
My question is: Are they any alternative/better ways to manage this exception ?
Thanks.
EDIT: I got my answer, can you please close this question.
Rather than working directly with Rectangles, how about adding a BodyPart class, with some manipulation methods:
public class BodyPart
{
private Rectangle rectangle;
public BodyPart(Rectangle rectangle)
{
this.rectangle = rectangle;
}
public void MoveY(int value)
{
rectangle.Y += value;
}
public void MoveX(int value)
{
rectangle.X += value;
}
}
public class Snake
{
public List<BodyPart> Parts = new List<BodyPart>();
}
public class AppSnake
{
public void Run()
{
var snake = new Snake();
snake.Parts.Add(new BodyPart(new Rectangle(760, 25, 8, 8)));
snake.Parts[0].MoveY(-10);
}
}
Rectangle, RectangleF, Point, PointF, Size and SizeF are value types (struct type). This means that you can not/should not change an individual part of the structure.
The reason is that unlike classes each variable keeps its own copy of the structure. When you type list[0].X = 10, the indexer list[0] returns a copy of the rectangle and not the original value. The correct way is to assign a new rectangle list[0] = A which copies all the values from A into the array.
Please read more on value types and structs before attempting to write code that uses them.
The quickest way to fix your code without completely changing it around is by adding methods that manipulate all the body parts in predefined ways:
public class Snake
{
public List<Rectangle> Parts { get; } = new List<Rectangle>();
public void MoveX(int delta)
{
for(int i = 0; i < Parts.Count; i++)
{
// Read the current rectangle from the list
var rect = Parts[i];
// Change the coordinate.
rect.X += delta;
// Write the modified rectangle back into the list
Parts[i] = rect;
}
}
public void MoveY(int delta)
{
for(int i = 0; i < Parts.Count; i++)
{
// Read the current rectangle from the list
var rect = Parts[i];
// Change the coordinate.
rect.Y += delta;
// Write the modified rectangle back into the list
Parts[i] = rect;
}
}
}
Use Rectangle.Location and Rectanagle.Size properties e.g.:
snk.bodyParts[0].Location = new Point(newX, newY);
snk.bodyParts[0].Size = new Size(newWidth, newHeight);

How can I draw a Hilbert Curve Fractal recursively using C# GDI+ Graphics and Windows Forms?

I am working on a project in which I need to use recursion to draw a Hilbert Curve fractal in a Windows Forms application in C#. I must use GDI+ graphics for this, but I am new to GDI+ graphics. Below is my entire code for the Form class that actually draws the curve. At the end of this post, I have included photos demonstrating my erroneous output and the expected output.
The DrawRelative() function is supposed to draw the next line segment from the current [x,y] coordinates to the new [x,y] coordinates, which are calculated by adding the xDistance and yDistance values passed into the DrawRelative() function to the xCurrent and yCurrent class properties.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace HilbertCurveFractal
{
public partial class FractalDisplay : Form
{
public int MaxDepth { get; set; }
public int CurveType { get; set; }
public int xCurrent { get; set; }
public int yCurrent { get; set; }
public int xLength { get; set; }
public int yLength { get; set; }
public FractalDisplay(int DepthValue, int SelectedCurve)
{
InitializeComponent();
MaxDepth = DepthValue;
CurveType = SelectedCurve;
xCurrent = 250;
yCurrent = 250;
xLength = 0;
yLength = 2;
}
private void FractalDisplay_Load(object sender, EventArgs e)
{
this.DoubleBuffered = true;
if (CurveType == 1) // Run the Hilbert Curve Generator
{
GenerateHilbertCurve(MaxDepth, xLength, yLength);
}
else if (CurveType == 2) // Run the Koch Curve Generator
{
}
else if (CurveType == 3) // Run the Sierpinski Curve Generator
{
}
else
{
MessageBox.Show("Error! - No Curve Type Selected. Ending Program.");
Application.Exit();
}
}
private void GenerateHilbertCurve(int depth, int xDistance, int yDistance)
{
//if (depth == 0) // Base Case
//{
// return;
//}
//else { }
if (depth > 0)
{
GenerateHilbertCurve(depth - 1, yDistance, xDistance);
}
else { }
// Draw Part of Curve Here
DrawRelative(xDistance, yDistance);
if (depth > 0)
{
GenerateHilbertCurve(depth - 1, xDistance, yDistance);
}
else { }
// Draw Part of Curve Here
DrawRelative(yDistance, xDistance);
if (depth > 0)
{
GenerateHilbertCurve(depth - 1, xDistance, yDistance);
}
else { }
// Draw Part of Curve Here
DrawRelative((- 1 * xDistance), (-1 * yDistance));
if (depth > 0)
{
GenerateHilbertCurve(depth - 1, (-1 * yDistance), (-1 * xDistance));
}
else { }
}
// Create a New Paint Event Handler
private void DrawRelative(int xDistance, int yDistance)
{
xLength = xDistance;
yLength = yDistance;
this.Paint += new PaintEventHandler(HilbertCurve_Paint);
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
// Discover where the new X and Y points will be
int xNew, yNew;
xNew = xCurrent + xLength;
yNew = yCurrent + yLength;
// Paint from the current position of X and Y to the new positions of X and Y
e.Graphics.DrawLine(Pens.Red, xCurrent, yCurrent, xNew, yNew);
// Update the Current Location of X and Y
xCurrent = xNew;
yCurrent = yNew;
}
}
}
The first photo (below) is the incorrect output from the Hilbert Curve function given a MaxDepth of 1.
The second photo (below) represents what I should be getting from this set of functions (given a MaxDepth value of 1 passed in).
Because it seems like the algorithm for recursion is coded correctly, I suspect that I am not using the GDI+ graphics in the proper way, or my class properties are being updated/set incorrectly somewhere in the recursive calls. What can I do to fix my drawing algorithm? Thank you in advance.
To be honest, I didn't initially understand the implementation you have for generating the points for the Hilbert curve. I'm familiar with a couple of different approaches, neither of which look like that.
But, that's an entirely different question. Your main issue at hand is really just that you don't understand how the drawing mechanism in Winforms works. Briefly: there's a Paint event, which your code should handle by drawing what needs to be drawn. Subscribing to the Paint event doesn't cause anything to happen; it's just a way of registering to be notified when drawing is supposed to occur.
Typically, one would use the Designer to subscribe to the event, by navigating to the "Events" tab of the Properties pane for an object in the Designer (e.g. your Form) and selecting the appropriate event handler (or double-clicking in the empty box next to the event to have the Designer automatically insert an empty handler for you to fill in). You can also, when handling the Paint event in your own object, simply override the OnPaint() method.
In either case, the correct technique is to establish the prerequisites for drawing, then call Invalidate() which causes the framework to then raise the Paint event, at which time you can actually draw what you want to draw.
Note that between commenter TaW and me, we have suggested two different approaches to drawing: I suggested pre-computing all of the necessary data for drawing, and then just draw that when the Paint event is raised; TaW has suggested calling the recursive method from the Paint event handler, and drawing directly as you traverse the recursive algorithm.
Both techniques are fine, but of course there are pros and cons to either, having mostly to do with the classic trade-off of time and space. With the former technique, the cost to generate the curve is incurred only once, when the parameters for the curve change. Drawing occurs more quickly, because all the code has to do is draw the pre-existing data. With the latter technique, there is no need to store the data, as each new point generated is used immediately, but of course this means all of the points have to be regenerated every time the window is redrawn.
For this particular application, in practice I don't think it matters much. At typical screen resolutions, you won't be able to make out the features of the curve long before you start to hit the limits of data storage for the points to draw. Similarly, the execution of the algorithm is so fast that there's really no harm in recalculating the points each time the window needs to be redrawn. Just keep in mind that these are trade-offs you may have to judge more closely in other scenarios.
Okay, so what's all that mean? Well, when I converted it to something that used the Graphics class correctly, I couldn't get your implementation to draw a Hilbert curve, so I changed that part of the code to use an implementation I know works. You can find a detailed discussion on how this particular implementation works here: Hilbert Curve
Concepts & Implementation
Below, I have provided two different versions of that particular Hilbert curve implementation, the first using the "retained" approach (i.e. generate the data, then draw it), and the second using the "immediate" approach (i.e. generate the data every time you want to draw the window, as the drawing is occurring):
"Retained" method:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private PointF[] _points;
private void FractalDisplay_Load(object sender, EventArgs e)
{
Redraw();
}
private void Redraw()
{
List<PointF> points = new List<PointF>();
GenerateHilbert(0, 0, 1, 0, 0, 1, (int)numericUpDown1.Value, points);
_points = points.ToArray();
Invalidate();
}
private void GenerateHilbert(PointF origin, float xi, float xj, float yi, float yj, int depth, List<PointF> points)
{
if (depth <= 0)
{
PointF current = origin + new SizeF((xi + yi) / 2, (xj + yj) / 2);
points.Add(current);
}
else
{
GenerateHilbert(origin, yi / 2, yj / 2, xi / 2, xj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2, xj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2 + yi / 2, xj / 2 + yj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, points);
GenerateHilbert(origin + new SizeF(xi / 2 + yi, xj / 2 + yj), -yi / 2, -yj / 2, -xi / 2, -xj / 2, depth - 1, points);
}
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (_points != null)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
e.Graphics.DrawLines(pen, _points);
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
"Immediate" method:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private void Redraw()
{
Invalidate();
}
private PointF GenerateHilbert(PointF origin, float xi, float xj, float yi, float yj, int depth,
PointF? previous, Graphics graphics, Pen pen)
{
if (depth <= 0)
{
PointF current = origin + new SizeF((xi + yi) / 2, (xj + yj) / 2);
if (previous != null)
{
graphics.DrawLine(pen, previous.Value, current);
}
return current;
}
else
{
previous = GenerateHilbert(origin, yi / 2, yj / 2, xi / 2, xj / 2, depth - 1, previous, graphics, pen);
previous = GenerateHilbert(origin + new SizeF(xi / 2, xj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, previous, graphics, pen);
previous = GenerateHilbert(origin + new SizeF(xi / 2 + yi / 2, xj / 2 + yj / 2), xi / 2, xj / 2, yi / 2, yj / 2, depth - 1, previous, graphics, pen);
return GenerateHilbert(origin + new SizeF(xi / 2 + yi, xj / 2 + yj), -yi / 2, -yj / 2, -xi / 2, -xj / 2, depth - 1, previous, graphics, pen);
}
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
GenerateHilbert(new PointF(), 1, 0, 0, 1, (int)numericUpDown1.Value, null, e.Graphics, pen);
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
In both examples I've made some other changes which are not strictly needed for the purpose of illustrating the techniques, but which are still useful:
The curve itself is computed in unit space (i.e. a square of side length of 1), and then drawn by scaling the drawing to fit the window.
Where it makes sense, individual coordinates are passed as whole PointF values instead. This simplifies reuse of the values and adding new offsets to the X and Y values.
Since the drawing is now scaled to the window, the window is redrawn if its size changes.
For simplicity, this Form is self-contained, with a NumericUpDownControl that determines the recursion depth. I didn't include instantiation of this control; I presume you can add the appropriate control yourself in the Designer, to make the above compile.
Addendum:
I've had a chance to look over the other examples on the Internet of the algorithm that you tried to implement. Now that I understand what the basic mechanism of the algorithm is, I was able to fix your version so that it works (the main problem was that you were using instance fields to store the deltas for the algorithm, but also using the same fields to initialize the algorithm, so once the algorithm ran once, subsequent executions wouldn't work). So for the sake of completeness, here is a second "retained" version of the code, using your preferred algorithm instead of the one I used above:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private PointF _previousPoint;
private PointF[] _points;
private void FractalDisplay_Load(object sender, EventArgs e)
{
Redraw();
}
private void Redraw()
{
List<PointF> points = new List<PointF>();
// Start here, to provide a bit of margin within the client area of the window
_previousPoint = new PointF(0.025f, 0.025f);
points.Add(_previousPoint);
int depth = (int)numericUpDown1.Value;
float gridCellCount = (float)(Math.Pow(2, depth) - 1);
// Use only 95% of the available space in the client area. Scale
// the delta for drawing to fill that 95% width/height exactly,
// according to the number of grid cells the given depth will
// produce in each direction.
GenerateHilbert3(depth, 0, 0.95f / gridCellCount, points);
_points = points.ToArray();
Invalidate();
}
private void GenerateHilbert(int depth, float xDistance, float yDistance, List<PointF> points)
{
if (depth < 1)
{
return;
}
GenerateHilbert(depth - 1, yDistance, xDistance, points);
DrawRelative(xDistance, yDistance, points);
GenerateHilbert(depth - 1, xDistance, yDistance, points);
DrawRelative(yDistance, xDistance, points);
GenerateHilbert(depth - 1, xDistance, yDistance, points);
DrawRelative(-xDistance, -yDistance, points);
GenerateHilbert(depth - 1, -yDistance, -xDistance, points);
}
private void DrawRelative(float xDistance, float yDistance, List<PointF> points)
{
// Discover where the new X and Y points will be
PointF currentPoint = _previousPoint + new SizeF(xDistance, yDistance);
// Paint from the current position of X and Y to the new positions of X and Y
points.Add(currentPoint);
// Update the Current Location of X and Y
_previousPoint = currentPoint;
}
// Perform the Actual Drawing
private void HilbertCurve_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (_points != null)
{
float scale = Math.Min(ClientSize.Width, ClientSize.Height);
e.Graphics.ScaleTransform(scale, scale);
using (Pen pen = new Pen(Color.Red, 1 / scale))
{
e.Graphics.DrawLines(pen, _points);
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Redraw();
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
Invalidate();
}
}
As before, I've modified your implementation slightly, so that the drawing is scaled to fit within the window at all depths. This involves drawing into the unit square and then setting the transform appropriately according to the window size.
In addition to fixing the basic usage of Graphics, and the issue with the xLength and yLength fields, I also fixed a minor bug in your code (where you were recursing one level too deep) and cleaned up the recursion a bit (there's no need to repeat the depth checkā€¦just do it once, at the beginning of the recursive method).
It is of course possible to implement this in the "immediate" style as well. I think between this new code example, and the "immediate" method example above, I can leave that exercise to the reader. :)
Here is the fractal generator I came up with after heeding the advice of #Peter Duniho - The code shown does not include the form that actually gets the depth level (maxDepth) of recursion requested by the user.
public partial class HilbertDisplay : Form
{
private int maxDepth;
private int xCurrent = 0;
private int yCurrent = 0;
private int xNew = 0;
private int yNew = 0;
public HilbertDisplay(int depthEntered)
{
InitializeComponent();
maxDepth = depthEntered;
}
private void HilbertDisplay_Load(object sender, EventArgs e)
{
this.DoubleBuffered = true;
this.Update();
}
// Perform the Drawing
private void HilbertDisplay_Paint(object sender, PaintEventArgs e)
{
// Run the Hilbert Curve Generator
// Use a line segment length of 10 for Y
GenerateHilbertCurve(maxDepth, 0, 10, e);
}
// The Recursive Hilbert Curve Generator
private void GenerateHilbertCurve(int depth, int xDistance, int yDistance, PaintEventArgs e)
{
if (depth < 1)
{
return;
}
else
{
GenerateHilbertCurve(depth - 1, yDistance, xDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(xDistance, yDistance);
e.Graphics.DrawLine(Pens.Red, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, xDistance, yDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(yDistance, xDistance);
e.Graphics.DrawLine(Pens.Blue, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, xDistance, yDistance, e);
// Paint from the current position of X and Y to the new positions of X and Y
FindPointRelative(-xDistance, -yDistance);
e.Graphics.DrawLine(Pens.Green, xCurrent, yCurrent, xNew, yNew); // Draw Part of Curve Here
UpdateCurrentLocation();
GenerateHilbertCurve(depth - 1, (-1 * yDistance), (-1 * xDistance), e);
}
}
private void FindPointRelative(int xDistance, int yDistance)
{
// Discover where the new X and Y points will be
xNew = xCurrent + xDistance;
yNew = yCurrent + yDistance;
return;
}
private void UpdateCurrentLocation()
{
// Update the Current Location of X and Y
xCurrent = xNew;
yCurrent = yNew;
return;
}
}
This code, unlike that of #Peter Duniho, does not account for the form's size. This depicts a Hilbert Curve fractal up to a recursion depth of 6 or 7 on my laptop (due to limitations on window size made by my laptop screen size/resolution).
I know that my solution is not as elegant as that of #Peter Duniho, but as this is for an assignment I did not want to simply copy his code. I made edits based on his suggestions, especially in regard to the Paint event.

Select drawn figure within panel box

I am working on an 'use-case-diagram-form' where an user can select an element and a modus
Just a simple form. It al works fine, made a class for each actor element and each use-case element. Both are added in a list after beeing created.
But somehow I just can't figure out how to select a created element and after do something with it.
classes i made:
class Actor
{
private static int _id;
private Panel _panel;
public string Name { get; private set; }
public int X { get; private set; }
public int Y { get; private set; }
public Actor(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
Name = name;
X = x;
Y = y;
}
public void DrawActor()
{
// draw Actor
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, X - 10, Y - 30, 20, 20);
graphics.DrawLine(pen, X, Y - 10, X, Y + 20);
graphics.DrawLine(pen, X - 15, Y, X + 15, Y);
graphics.DrawLine(pen, X, Y + 20, X - 15, Y + 35);
graphics.DrawLine(pen, X, Y + 20, X + 15, Y + 35);
// rectangle around actor
graphics.DrawRectangle(pen, (X - 20), (Y - 30), 40, 80);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(Name, stringFont).Width;
// label
var label = new Label();
var actorText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + Name;
label.Text = actorText;
label.Location = new Point(X - (Convert.ToInt32(textWith)/2), Y + 40);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
class UseCase
{
private static int _id;
private Panel _panel;
private string _name;
private int _x;
private int _y;
public UseCase(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
_name = name;
_x = x;
_y = y;
}
public void DrawUseCase()
{
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, _x , _y , 120, 50);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(_name, stringFont).Width;
// label
var label = new Label();
var useCaseText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + _name;
label.Text = useCaseText;
label.Location = new Point(_x - (Convert.ToInt32(textWith) / 2) + 60, _y + 20);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
}
Github repository:
https://github.com/JimVercoelen/use-case-helper
Thanks
Your code has several issues, all of which will go away once you learn how to draw properly in winforms!
There are many posts describing it but what you need to understand that you really have these two options:
Draw onto the surface of the control. This is what you do, but you do it all wrong.
Draw into a Bitmap which is displayed in the control, like the Picturbox's Image or a Panel's BackgroundImage.
Option two is best for graphics that slowly add up and won't need to be corrected all the time.
Option one is best for interactive graphics, where the user will move things around a lot or change or delete them.
You can also mix the options by caching drawn graphics in a Bitmap when they get too numerous.
Since you started with drawing onto the surface let's see how you should do it correctly:
The Golden Rule: All drawing needs to be done in the control's Paint event or be triggered from there always using only the Paint event's e.Graphics object!
Instead you have created a Graphics object by using control.CreateGraphics. This is almost always wrong.
One consequence of the above rule is that the Paint event needs to be able to draw all objects the user has created so far. So you will need to have class level lists to hold the necessary data: List<ActorClass> and List<UseCaseClass>. Then it can do maybe a
foreach(ActorClass actor in ActorList) actor.drawActor(e.Graphics)
etc.
Yes this fully repainting everything looks like a waste but it won't be a problem until you need to draw several hundreds of object.
But if you don't do it this way, nothing you draw persists.
Test it by running your present code and doing a Minimize/Maximize sequence. Poof, all drawings are gone..
Now back to your original question: How to select an e.g. Actor?
This really gets simple as you can can iterate over the ActorList in the MouseClick event (do not use the Click event, as it lacks the necessary parameters):
foreach (ActorClass actor in ActorList)
if (actor.rectangle.Contains e.Location)
{
// do stuff
break;
}
This is a simple implementation; you may want to refine it for the case of overlapping objects..
Now you could do things like maybe change the color of the rectangle or add a reference to the object in a currentActor variable.
Whenever you have made any changes to your lists of things to draw, like adding or deleting a object or moving it or changing any (visible) properties you should trigger an update via the Paint event by calling Invalidate.
Btw: You asked about a PictureBox in the title but use only a Panel in the code. Using a PictureBoxis recommended as it is doublebuffered and also combines two Bitmaps to let you use both a caching Image and a BackgroundImage with maybe a nice paper..
As far as I can see your code so far lacks the necessary classes. When you write them add a Draw routine and either a reference to the Label you add or simply use DrawString to draw the text yourself..
Update:
After looking at your project, here a the minimal changes to make the drawing work:
// private Graphics graphics; // delete!!
Never try to cache a Graphics object!
private void pl_Diagram_Paint(object sender, PaintEventArgs e)
{
pen = new Pen(Color.Black, 1);
DrawElements(e.Graphics); // pass out the 'good' object
//graphics = pl_Diagram.CreateGraphics(); // delete!
}
The same; pass the real Graphics object into the drawing routine instead!
// actor
if (rb_Actor.Checked)
{
if (e.X <= 150)
{
var actor = new Actor(name, e.X, e.Y);
_actors.Add(actor);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
}
// use case
if (rb_Use_Cases.Checked)
{
var useCase = new UseCase(name, e.X, e.Y);
_useCases.Add(useCase);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
Instead of calling the routine directly we trigger the Paint event, which then can pass a good Graphics object to it.
public void DrawElements(Graphics graphics)
{
foreach (var actor in _actors)
{
DrawActor(graphics, actor);
}
foreach (var useCase in _useCases)
{
DrawUseCase(graphics, useCase);
}
}
We receive the Graphics object and pass it on..
private void DrawActor(Graphics graphics, Actor actor)
and
graphics.DrawEllipse(pen, (useCase.X - 60), (useCase.Y - 30), 120, 60);
After these few changes the drawing persists.
Replacing the Panel by a Picturebox is still recommended to avoid flicker during the redraw. (Or replace by a double-buffered Panel subclass..)

Draw fractal Fit inside panel using Winform?

i am trying to build a windows application in .net which draw fractal image inside the panel.It take end points of line as starting point of next line.But problem is, diagram is going outside of the panel.How do i fix drawing inside the panel
static int start_x, start_Y;
static int end_x, end_Y;
static int my_angle = 0;
static int my_length = 0;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
start_x = Canvas.Width / 2;
start_Y = Canvas.Height / 2;
for (int i = 0; i < 400; i++)
{
draw_T();
}
}
public void draw_T()
{
Pen mypen = new Pen(Color.Green, 2F);
my_angle = my_angle + (45);
my_length = 100 + (1);
end_x = (int)(start_x + Math.Cos(my_angle * .0174539676) * my_length);
end_Y = (int)(start_Y + Math.Sin(my_angle * .0174539676) * my_length);
Point[] points =
{
new Point (start_x,start_Y),
new Point (end_x,end_Y)
};
Point[] points1 =
{
new Point ((end_x+start_x)/2,(end_Y+start_Y)/2),
new Point (end_x+50,end_Y-100)
};
start_x = end_x;
start_Y = end_Y;
Graphics g = Canvas.CreateGraphics();
g.DrawLines(mypen, points);
g.DrawLines(mypen, points1);
}
I'm not sure how you graphic is supposed to look but I can give you a couple of hints.
At general one first: Do make use of e.Graphics parameter! Change
public void draw_T()
To
public void draw_T(Graphics g)
and delete the line.
Graphics g = Canvas.CreateGraphics();
Change the call to
draw_T(e.Graphics);
You are leaking GDI resource by creating all those Graphcs with disposing of them and and lose time by creating them when you already have the one from the Paint event.
Next you should add a NumericUpDown for testing your algorithm and script it like this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Canvas.Invalidate();
}
To work you now change the loop to
for (int i = 0; i < numericUpDown1.Value; i++)
And watch your graphics develop.
Another test could be to introduce a second pen color for the second series of point.
To play around further you could add another NumericUpDown and tie my_lengthto it..
In the end you'll see that length needs to be smaller than 101 or the Canvas needs to be as large as 700 pixels.
BTW: Neither my_angle nor my_length need to be declared at class level since they are always set in the method and used nowhere else and no other variable needs to static either, at least from what you show us..

Why is DrawRectangle drawing a cross inside my PictureBox

I'm trying to draw 10 rectangles, but when I use g.DrawRectangle() it is drawing a cross as shown below:
I'm creating Vertex objects that contain a getRectangle() function which returns a Rectangle object for that vertex.
I was hoping to create these objects and show them as Rectangles on the pictureBox.
Here's my code
private System.Drawing.Graphics g;
private System.Drawing.Pen pen1 = new System.Drawing.Pen(Color.Blue, 2F);
public Form1()
{
InitializeComponent();
pictureBox.Dock = DockStyle.Fill;
pictureBox.BackColor = Color.White;
}
private void paintPictureBox(object sender, PaintEventArgs e)
{
// Draw the vertex on the screen
g = e.Graphics;
// Create new graph object
Graph newGraph = new Graph();
for (int i = 0; i <= 10; i++)
{
// Tried this code too, but it still shows the cross
//g.DrawRectangle(pen1, Rectangle(10,10,10,10);
g.DrawRectangle(pen1, newGraph.verteces[0,i].getRectangle());
}
}
Code for Vertex class
class Vertex
{
public int locationX;
public int locationY;
public int height = 10;
public int width = 10;
// Empty overload constructor
public Vertex()
{
}
// Constructor for Vertex
public Vertex(int locX, int locY)
{
// Set the variables
this.locationX = locX;
this.locationY = locY;
}
public Rectangle getRectangle()
{
// Create a rectangle out of the vertex information
return new Rectangle(locationX, locationY, width, height);
}
}
Code for Graph class
class Graph
{
//verteces;
public Vertex[,] verteces = new Vertex[10, 10];
public Graph()
{
// Generate the graph, create the vertexs
for (int i = 0; i <= 10; i++)
{
// Create 10 Vertexes with different coordinates
verteces[0, i] = new Vertex(0, i);
}
}
}
Looks like an exception in your draw loop
last call to:
newGraph.verteces[0,i]
fails with OutOfRangeException
you shoul iterate not to i <= 10, but to i < 10
Red Cross Indicates that an Exception has been thrown, you are not seeing it because it's being handled. Configure Visual Studio to break on exception throw to catch it.
An exception has been thrown. At first look your code:
for (int i = 0; i <= 10; i++)
will generate an IndexOutOfRangeException because verteces has 10 items but it will cycle from 0 to 10 (included so it'll search for 11 elements). It depends on what you want to do but you have to change the cycle to (removing the = from <=):
for (int i = 0; i < 10; i++)
or to increment the size of verteces to 11.

Categories

Resources