I'm creating a Graphical Editor. I'm able to draw lines and rectangles but now I want to move them, so I am trying to add the MouseMove event now. I tried the following things:
rectangle.MouseMove += shape_MouseMove;
Error:
'System.Drawing.Rectangle' does not contain a definition for 'MouseDown' and no extension method 'MouseDown' accepting a first argument of type 'System.Drawing.Rectangle' could be found (are you missing a using directive or an assembly reference?)
rectangle += shape_MouseMove;
Errors:
Error 2 Cannot convert method group 'shape_MouseMove' to non-delegate type 'System.Drawing.Rectangle'. Did you intend to invoke the method
Error 1 Operator '+=' cannot be applied to operands of type 'System.Drawing.Rectangle' and 'method group'
Code:
private void shape_MouseMove(object sender, MouseEventArgs e)
{
}
private void panel_MouseUp(object sender, MouseEventArgs e)
{
draw = false;
xe = e.X;
ye = e.Y;
Item item;
Enum.TryParse<Item>(menuComboBoxShape.ComboBox.SelectedValue.ToString(), out item);
switch (item)
{
case Item.Pencil:
using (Graphics g = panel.CreateGraphics())
using (var pen = new Pen(System.Drawing.Color.Black)) //Create the pen used to draw the line (using statement makes sure the pen is disposed)
{
g.DrawLine(pen, new Point(x, y), new Point(xe, ye));
}
break;
case Item.Rectangle:
Rectangle rectangle = new Rectangle(x, y,xe-x, ye-y);
rectangle += shape_MouseMove; //Error here
using (Graphics g = panel.CreateGraphics())
using (var pen = new Pen(System.Drawing.Color.Black)) //Create the pen used to draw the rectangle (using statement makes sure the pen is disposed)
{
g.DrawRectangle(pen,rectangle);
}
break;
default:
break;
}
}
How can I add the MouseMove event to the Rectangle?
You can't solve your problem as it stands.
Rectangle is just a structure, intended to describe some region on the screen. To avoid problems with manual mouse/keyboard handling, you need to wrap shapes into UserControls, and implement shape-specific painting.
This also will use OOP benefits. Instead of switch/case you will get a set of shape types, and every type will be responsible to draw its instances:
public partial class RectangularShape : UserControl
{
public RectangularShape()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.Red, ClientRectangle);
}
}
simple as it is ... you can't
you can however create a subclass of Control or UserControl, and create your own type of rectangle with all that fancy UI support...
You should create a class Shape with all the info needed to draw itself.
Then you should have a List<Shape> and use the Paint event of the Panel to draw all the Shapes.
Using Controls, no matter which, will always run into the tranparency problems inherent to Winforms GDI+ drawing. This means that overlapping controls will not be able to fake transparency well.. Real transparency between controls is not possible at all and faked transparency only works between nested controls.
The Shape class would contain fields for its types, the location and size, colors and penstyle, text and font and maybe many more things your various shapes need to know for drawing themselves.
To edit, change, delete those shapes you will need to process the shapes list and upon MouseDown find the one that was hit; then you can follow it up with MouseMove or enter changing values for the other properties..
How to do that precisely is always a matter of taste; commercial programs have rather different methods to resolve stacking conflicts: Some pick a shape when you click near its border, other cycle though overlapping shapes with each click..
You should also plan for ways to change the z-order of the shapes..
Have fun!
Related
I'm experiencing a discrepancy between a GraphicsPath drawn in World coordinates on a UserControl and the results of GraphicsPath.IsVisible() to Hit Test the shape with the mouse.
I performed a little test that made a map of where IsVisible() returned true, relative to the GraphicsPath shape that was drawn. The results show a very "low resolution" version of the shape I'm drawing.
Link to shared Google Drive image showing the results:
http://goo.gl/zd6xiM
Is there something I'm doing or not doing correctly that's causing this?
Thanks!
Here's the majority of my OnMouseMove() event handler:
protected override void OnMouseMove(MouseEventArgs e)
{
//base.OnMouseMove(e);
debugPixel = Point.Empty;
PointF worldPosition = ScreenToWorld(PointToClient(Cursor.Position));
if (_mouseStart == Point.Empty) // Just moving mouse around, no buttons pressed
{
_objectUnderMouse = null;
// Hit test mouse position against each canvas object to see if we're overtop of anything
for (int index = 0; index < _canvasObjects.Count; index++) // Uses front to back order
{
NPCanvasObject canvasObject = _canvasObjects[index];
if (canvasObject is NPCanvasPart)
{
NPCanvasPart canvasPart = (canvasObject as NPCanvasPart);
NPPart part = canvasPart.Part;
GraphicsPath gp = canvasPart.GraphicsPath;
// Set the object under the mouse cursor, and move it to the "front" so it draws on top of everythign else
if (gp.IsVisible(worldPosition))
{
// DEBUG
debugPixel.X = e.X;
debugPixel.Y = e.Y;
_objectUnderMouse = canvasObject;
_canvasObjects.MoveItemAtIndexToFront(_canvasObjects.IndexOf(canvasObject));
break; // Since we're modifying the collection we're iterating through, we can't reliably continue past this point
}
}
}
}
else
{
...
}
}
Later in my drawing code I draw a pixel whenever debugPixel != Point.Empty . I temporarily suppressed clearing before drawing so I could see them all.
Some other info that may be asked, or could be helpful to troubleshoot:
I've tried different Graphics.InterpolationMode settings but that doesn't seem to have any effect
I've applied a TranslateTransform and ScaleTransform to the main drawing Graphics but the underlying HitTest map seems to scale and translate equal to the GraphicsPath
For my main drawing canvas, Graphics.PageUnit = GraphicsUnit.Inch, except when I'm doing pixel-based overlay stuff
I thought I had researched this thoroughly enough, but apparently not. Shortly after posting this question I did another search with slightly different terms and found this:
http://vbcity.com/forums/t/72877.aspx
...which was enough to clue me in that the GraphicsPath and my main drawing Graphics were not the same. Using the overloaded GraphicsPath.IsVisible(PointF, Graphics) solved this problem very nicely.
Essentially it was trying to check against a very aliased (pixelated) version of my shape that had been scaled to the same size but not smoothed.
so I'm trying to find a way to draw a straight line between two buttons that I have clicked on (There are multiple source->destination lines to draw). I am currently using this code.
private void Form1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
foreach (Connection c in connections)
{
Point pt1 = c.source.Location;
Point pt2 = c.destination.Location;
using (Pen p = new Pen(Brushes.Black))
{
g.DrawLine(p, pt1, pt2);
}
}
}
}
Now this works, but obviously it is drawing on my form canvas and it hidden behind all my buttons that are on my form. Here is what the layout looks like:
Is there anyway I can fix this?
Thanks.
Each button knows its relative position on parent and each can handle its Paint event. If you store your lines in some colletion in form of their line equation that goes through two points (x2-x1)(y-y1)=(y2-y1)(x-x1), you will be able to iterate through them in button Paint handler and calculate whether the line crosses the button's edges or not. Each button should have own equation of its edges with respect to its parent coordinates.
Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).
I've had a look around the web regarding this, but haven't found the exact answer I'm looking for or I've tried what is suggested and it doesn't work!
I'm having issues in that I have a screen which has approximately 72 Checkboxes on it in a matrix which I have connected together using lines the coordinates of which I store in a list.
To draw the lines I use the Drawline method in an override method for OnPaint to iterate through the list as follows :-
protected override void OnPaint(PaintEventArgs e)
{
Pen myPen = new Pen(System.Drawing.Color.Black);
Graphics g = this.CreateGraphics();
myPen.Width = 5;
foreach(ConnectionLine cl in connectionLines)
{
g.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
myPen.Dispose();
g.Dispose();
}
The strange thing about this is that it doesn't appear to be the lines that take the time to draw - it's now the checkboxes, if I remove the line functionality these refresh in the blink of an eye.
Any ideas much appreciated.
Thanks,
Dave
Part of the problem could be that you are recreating the Graphics object each time the control is painted. Instead you should use the e.Graphics object that is provided in PaintEventArgs. You could also try using only one instance of Pen.
private readonly Pen _myPen = new Pen(System.Drawing.Color.Black) {Width = 5};
protected override void OnPaint(PaintEventArgs e)
{
foreach (var cl in connectionLines)
e.Graphics.DrawLine(_myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
There is no need to create your own and dispose of a Graphics object. Use what is available with the event handler. ALso you should use using rather than calling Dispose explicitly.
protected override void OnPaint(PaintEventArgs e)
{
using (Pen myPen = new Pen(System.Drawing.Color.Black, 5.0))
{
foreach(ConnectionLine cl in connectionLines)
e.Graphics.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
}
Also, if your lines connect you should get better performance and cleaner code with Graphic's DrawLines method. You would have to change how you store your points or extract them from your conncetionLines collection before calling.
How do I draw a circle and line in the picturebox?
or:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(
new Pen(Color.Red,2f),
new Point(0,0),
new Point(pictureBox1.Size.Width, pictureBox1.Size.Height ));
e.Graphics.DrawEllipse(
new Pen(Color.Red, 2f),
0,0, pictureBox1.Size.Width, pictureBox1.Size.Height );
}
Handle the paint event of the picture box and do your custom drawing there.
The best way is to NOT draw a circle and line in a picturebox! It is not designed for that purpose.
From Bob Powell's GDI+ blog:
The root of this problem is that the fundamental rules of windows
programming have been broken. And as a consequence of the picture box
is blamed for something that's really not its fault. To help explain
why, the four points below outline what's gone wrong in this case.
The PictureBox control is for displaying images. It is not a handy placeholder for a graphics surface.
Windows is an event driven system in which each event must be serviced in the correct context and events destined to handle button click or mouse move events must not be used to do drawing on screen or other weird stuff.
The PictureBox refreshes itself by drawing the System.Drawing.Image based object stored in it's Image property. If there is no image, it will show the background colour.
Stealing and drawing upon the Graphics object of any control is not good practice, should be strongly discouraged and breaks the rules of handling events in the right place at the right time. Basically if you do this it will cause you pain. When you bang your head against a wall it causes you pain. that is a sign that you should stop doing it. It's the same for the PictureBox.CreateGraphics call.
The right way to do it.
Following the rules of the event driven system is easy but requires a
little forethought. So, if you want to draw some little bit of
graphics and have it remain there when a window moves in front of it
and away again or when you minimize and restore, you have to service
the Paint event of whatever object it is that you wish to paint on.
The PictureBox carries baggage around with it that is unnecessary for
this kind of application. If you just want to draw something in one
place, draw it on the form by responding to the Form.Paint event. If
you want a handy placeholder for a graphic that works within a set
bounds, use a Panel control and service it's Paint event. If you want
to duplicate a graphic over and over for your corporate image, create
a control and do the drawing in the OnPaint override.
Source: https://web.archive.org/web/20120330003635/http://bobpowell.net/picturebox.htm (the original site is defunct).
the picturebox is a control and has an image as source - so you have to draw on the image and hand the image to the control to show it
MyImage = new Bitmap(fileToDisplay);
pictureBox1.ClientSize = new Size(xSize, ySize);
pictureBox1.Image = MyImage;
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 Asssignment
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Graphics g = this.CreateGraphics();
Pen p = new Pen(Color.Blue);
int radius = 200;
int x =Width/2;
int y =Height/2;
int first_point1 = (int)(Math.Cos(0) * radius + x);
int first_point2 = (int)(Math.Sin(0) * radius + y);
Point p1= new Point(first_point1,first_point2);
for(int i=1;i<500; i++)
{
int dx = (int)(Math.Cos(i)*radius+x );
int dy = (int)(Math.Sin(i)*radius+y );
Point p2 = new Point(dx, dy);
g.DrawLine(p, p1, p2);
p1 = p2;
}
}
}
}