I borrowed some code to draw a rectangle on an image, e.g. like a selection box. Right now, the code draws a rectangle any time you click and drag your mouse. If you simply left-click without dragging, nothing at all happens - the existing rectangle stays put. If you click and drag a new rectangle, the old rectangle disappears.
That's almost exactly like I want (I'm not wanting to permanently draw on the image... yet...), but with one change: I'd like for a single left-click to make the rectangle disappear as well.
The code is as follows:
public partial class ScreenSelection : Form
{
private Point RectStartPoint;
private Rectangle Rect = new Rectangle();
private Brush selectionBrush = new SolidBrush(Color.FromArgb(128, 72, 145, 220));
public ScreenSelection(DataTable buttonData)
{
InitializeComponent();
}
private void Canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
RectStartPoint = e.Location;
Invalidate();
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
Canvas.Invalidate();
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
// Draw the rectangle...
if (Canvas.Image != null)
{
if (Rect != null && Rect.Width > 0 && Rect.Height > 0)
{
e.Graphics.FillRectangle(selectionBrush, Rect);
}
}
}
}
I also have the user load a bitmap in as the image of the canvas, so once the user does that, canvas.image won't equal null.
so how can I make that rectangle disappear on a left click? I'm already doing an invalidate on the left-click, and that's clearly not getting rid of it.
I tried forcing the rectangle size on a left-click by doing:
if (e.Button == MouseButtons.Left)
{
RectStartPoint = e.Location;
Rect.Height = 0;
Rect.Width = 0;
Invalidate();
}
and tried Rect.Size, Rect = Rectangle.Empty, Canvas.Refresh()...
How can I accomplish this?
Edit:
I've also tried saving the graphics state and restoring it. That doesn't work... (no errors, just not getting rid of the rectangle)
Finally found a way to do it where I keep the drawing inside the paint event to improve performance / remove flicker...
It all had to do with fillRectangles
here's the working code:
public partial class ScreenSelection : Form
{
private Point RectStartPoint;
private Rectangle Rect = new Rectangle();
private Brush selectionBrush = new SolidBrush(Color.FromArgb(128, 72, 145, 220));
private List<Rectangle> Rects = new List<Rectangle>();
private bool RectStart = false;
public ScreenSelection(DataTable buttonData)
{
InitializeComponent();
}
private void Canvas_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (RectStartPoint == e.Location)
{
int i = Rects.Count;
if (i > 0) { Rects.RemoveAt(i - 1); }
Canvas.Refresh();
}
}
}
private void Canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
RectStartPoint = e.Location;
int i = Rects.Count;
if (i >= 1) { Rects.RemoveAt(i - 1); }
RectStart = false;
Canvas.Refresh();
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
if (!RectStart)
{
Rects.Add(Rect);
RectStart = true;
}
else
{
Rects[(Rects.Count - 1)] = Rect;
}
Canvas.Invalidate();
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
// Draw the rectangle...
if (Canvas.Image != null)
{
if (Rects.Count > 0)
{
e.Graphics.FillRectangles(selectionBrush, Rects.ToArray());
}
}
}
}
The bonus to this is that if I'm careful, I'll be able to also make some rectangles permanent while removing others.
Related
How do I check if a mouse clicked a rectangle?
Graphics gfx;
Rectangle hitbox;
hitbox = new hitbox(50,50,10,10);
//TIMER AT THE BOTTOM
gfx.Draw(System.Drawing.Pens.Black,hitbox);
Just a sample quick and dirty, if your "gfx" is a "e.Graphics..." from a Form:
public partial class Form1 : Form
{
private readonly Rectangle hitbox = new Rectangle(50, 50, 10, 10);
private readonly Pen pen = new Pen(Brushes.Black);
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(pen, hitbox);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if ((e.X > hitbox.X) && (e.X < hitbox.X + hitbox.Width) &&
(e.Y > hitbox.Y) && (e.Y < hitbox.Y + hitbox.Height))
{
Text = "HIT";
}
else
{
Text = "NO";
}
}
}
Rectangle has several handy but often overlooked functions. In this case using the Rectangle.Contains(Point) function is the best solution:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (hitbox.Contains(e.Location)) .. // clicked inside
}
To determine if you clicked on the outline you will want to decide on a width, since the user can't easily hit a single pixel.
For this you can use either GraphicsPath.IsOutlineVisible(Point)..
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
GraphicsPath gp = new GraphicsPath();
gp.AddRectanle(hitbox);
using (Pen pen = new Pen(Color.Black, 2f))
if (gp.IsOutlineVisible(e.location), pen) .. // clicked on outline
}
..or stick to rectangles..:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Rectangle inner = hitbox;
Rectangle outer = hitbox;
inner.Inflate(-1, -1); // a two pixel
outer.Inflate(1, 1); // ..outline
if (outer.Contains(e.Location) && !innerContains(e.Location)) .. // clicked on outline
}
I'm using WinForms. In my form i have a picturebox with an image. How can i paint the picturebox but not the area inside the expanding square. Here is my code. Currently i could create the expanding square but i don't know how to paint the picturebox white outside of that square.
int _cropX, _cropY, _cropWidth, _cropHeight;
private State _currentState;
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (Crop_Checkbox.Checked == true)
{
if (_currentState == State.Crop)
{
Cursor = Cursors.Cross;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
//X and Y are the coordinates of Crop
pictureBox1.Refresh();
_cropWidth = e.X - _cropX;
_cropHeight = e.Y - _cropY;
pictureBox1.CreateGraphics().DrawRectangle(_cropPen, _cropX, _cropY, _cropWidth, _cropHeight);
}
}
}
else
{
Cursor = Cursors.Default;
}
}
private void Crop_Checkbox_CheckedChanged(object sender, EventArgs e)
{
if (Crop_Checkbox.Checked == true)
{
this.Cursor = Cursors.Cross;
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (Crop_Checkbox.Checked == true)
{
if (_currentState == State.Crop)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Cursor = Cursors.Cross;
_cropX = e.X;
_cropY = e.Y;
_cropPen = new Pen(Color.FromArgb(153, 180, 209), 3); //2 is Thickness of line
_cropPen.DashStyle = DashStyle.DashDotDot;
pictureBox1.Refresh();
}
}
}
else
{
Cursor = Cursors.Default;
}
}
public Pen _cropPen;
private enum State
{
Crop
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (Crop_Checkbox.Checked == true)
{
//Paint picturebox...
}
else
{
Cursor = Cursors.Default;
}
}
This is best done with a GraphicsPath:
using System.Drawing.Drawing2D;
..
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle r1 = pictureBox1.ClientRectangle; // note I don't use width or height!
Rectangle r2 = new Rectangle(50, 30, 80, 40);
GraphicsPath gp = new GraphicsPath(FillMode.Alternate);
gp.AddRectangle(r1); // first the big one
gp.AddRectangle(r2); // now the one to exclude
e.Graphics.FillPath( Brushes.Gold, gp);
}
Note that I..
..use the Paint event for persistent grphics
..only paint onto the surface of the PictureBox, not into its image. See here for the difference!
You can add more rectangles or other shapes to exclude.
If you want image and surface combined, either draw into the image or ask the PictureBox to DrawToBitmap..
I'm really new to drawing in C#, and I'm using Windows Forms instead of WPF, so maybe I'm doing it wrong from the get-go... you tell me... but I'd like to have a temporary marker put down on the PictureBox (on MouseDown) that will follow the mouse (erasing the previous drawings of itself, i.e. not leaving a trail), and then being drawn in the final position on the MouseUp event.
Here's some skeleton code that you guys can fill in:
bool mDown;
Graphics g; // initialized to pictureBox1.CreateGraphics() on Form_Load, though
// I am unsure how that differs from Graphics.FromImage(pictureBox1)
SolidBrush sbGray, sbGreen;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
mDown = true;
// store/push initial drawing
g.FillEllipse(sbGray, e.X - 5, e.Y - 5, 10, 10);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (mDown)
{
// restore/pop initial drawing, erasing old trail
g.FillEllipse(sbGray, e.X - 5, e.Y - 5, 10, 10);
}
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
// restore/pop initial drawing, erasing old trail
g.FillEllipse(sbGreen, e.X - 5, e.Y - 5, 10, 10);
mDown = false;
}
I suppose there are several ways to skin a cat, such as changing the mouse icon, so perhaps this is not even the best way to do it? Even so, I will probably need to know the answers to both questions -- whether there is a better way to do it, and also how to extract the graphics from a PictureBox (as this knowledge will most likely prove useful later anyways.)
(Note: Although the gray circles should erase themselves, the green circles should be persistent and multiple green circles should be capable of existing in the PictureBox at the same time.)
You need to look at the PictureBox's Paint Event also, it is better to do all of your graphics in the Paint event since you do not have to worry about disposing the Graphic Object.. See if this is what you were wanting.
Edit: added code to retain points and also to clear them.
public partial class Form1 : Form
{
bool mDown;
SolidBrush sbGray = new SolidBrush(Color.Gray);
SolidBrush sbGreen = new SolidBrush(Color.LimeGreen);
SolidBrush sbTemp;
Point mPosition = new Point();
public List<Point> points = new List<Point>();
public Form1()
{
InitializeComponent();
pictureBox1.Image = Image.FromFile(#"C:\Temp\Test.jpg");
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
mDown = true;
mPosition = new Point(e.X, e.Y);
sbTemp = sbGray;
pictureBox1.Invalidate();
}
else
{
points.Clear();
sbTemp = null;
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (mDown)
{
mPosition = new Point(e.X, e.Y);
sbTemp = sbGray;
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
mPosition = new Point(e.X, e.Y);
points.Add(mPosition);
sbTemp = sbGreen;
pictureBox1.Invalidate();
mDown = false;
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (sbTemp != null)
{
e.Graphics.FillEllipse(sbTemp, mPosition.X -5, mPosition.Y -5, 10, 10);
}
if (points != null)
{
foreach (var loc in points)
{
e.Graphics.FillEllipse(sbGreen, loc.X - 5, loc.Y - 5, 10, 10);
}
}
}
}
i just wanted to put a selection on my picturebox.image but this has just become worse than some little annoying situation. I thought on another picture box over the main picturebox but it seemed so lazy work to me. I need to know if there is a way to create a selection area (which is gonna be half transparent blue area) on a picturebox.image which im gonna draw with mouse and it shouldnt change the image im working on.
sample:
// Start Rectangle
//
private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Determine the initial rectangle coordinates...
RectStartPoint = e.Location;
Invalidate();
}
// Draw Rectangle
//
private void pictureBox1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect =
new Rectangle(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y),
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
Invalidate(Rect);
}
// Draw Area
//
private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
// Draw the rectangle...
if (pictureBox1.Image != null)
{
Brush brush = new SolidBrush(Color.FromArgb(128, 72, 145, 220));
e.Graphics.FillRectangle(brush, Rect);
}
}
I used your code, you were nearly there. You needed to Invalidate the pictureBox1 instead of the rectangle. I also added a check for the Rect so it doesn't get drawn when it's not initialized or has no size.
Another important change: I created the Rectangle only once and I adjusted its location and size. Less garbage to clean up!
EDIT
I added a mouse right-click handler for the Rectangle.
private Point RectStartPoint;
private Rectangle Rect = new Rectangle();
private Brush selectionBrush = new SolidBrush(Color.FromArgb(128, 72, 145, 220));
// Start Rectangle
//
private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Determine the initial rectangle coordinates...
RectStartPoint = e.Location;
Invalidate();
}
// Draw Rectangle
//
private void pictureBox1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
pictureBox1.Invalidate();
}
// Draw Area
//
private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
// Draw the rectangle...
if (pictureBox1.Image != null)
{
if (Rect != null && Rect.Width > 0 && Rect.Height > 0)
{
e.Graphics.FillRectangle(selectionBrush, Rect);
}
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
if (Rect.Contains(e.Location))
{
Debug.WriteLine("Right click");
}
}
}
private int xUp, yUp, xDown,yDown;
private Rectangle rectCropArea;
private void SrcPicBox_MouseUp(object sender, MouseEventArgs e)
{
//pictureBox1.Image.Clone();
xUp = e.X;
yUp = e.Y;
Rectangle rec = new Rectangle(xDown,yDown,Math.Abs(xUp xDown),Math.Abs(yUp-yDown));
using (Pen pen = new Pen(Color.YellowGreen, 3))
{
SrcPicBox.CreateGraphics().DrawRectangle(pen, rec);
}
rectCropArea = rec;
}
private void SrcPicBox_MouseDown(object sender, MouseEventArgs e)
{
SrcPicBox.Invalidate();
xDown = e.X;
yDown = e.Y;
}
private void btn_upload_Click(object sender, EventArgs e)
{
OpenFileDialog opf = new OpenFileDialog();
// PictureBox SrcPicBox = new PictureBox();
opf.Filter = "ALL images(*.*)|*.*";
if (opf.ShowDialog() == DialogResult.OK)
{
string name = opf.SafeFileName;
string filepath = opf.FileName;
File.Copy(filepath, name, true);
SrcPicBox.Image = Image.FromFile(opf.FileName);
}
private void btn_crop_Click(object sender, EventArgs e)
{
pictureBox3.Refresh();
//Prepare a new Bitmap on which the cropped image will be drawn
Bitmap sourceBitmap = new Bitmap(SrcPicBox.Image, SrcPicBox.Width, SrcPicBox.Height);
Graphics g = pictureBox3.CreateGraphics();
//Draw the image on the Graphics object with the new dimesions
g.DrawImage(sourceBitmap, new Rectangle(0, 0, pictureBox3.Width, pictureBox3.Height), rectCropArea, GraphicsUnit.Pixel);
sourceBitmap.Dispose();
}
Download the project
I am trying to make a panel with a background color which should be able to be drawn in runtime when the user holds down left mouse button and moves it around. All works find when the user is starting from top left and go to bottom right just like the image shows:
But I want the user to be able to make the panel from bottom right to top left. Just like when you select something on your computer with your mouse
Here is my code for now:
public void parent_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point tempLoc = e.Location;
this.Location = new Point
(
Math.Min(this.Location.X, tempLoc.X),
Math.Min(this.Location.Y, tempLoc.Y)
);
this.Size = new Size
(
Math.Abs(this.Location.X - tempLoc.X),
Math.Abs(this.Location.Y - tempLoc.Y)
);
this.Invalidate();
}
}
I think this is where I go wrong, and I simply can't find the right algorithm for it:
this.Size = new Size
(
Math.Abs(this.Location.X - tempLoc.X),
Math.Abs(this.Location.Y - tempLoc.Y)
);
But if I use a rectangle it works fine, but I want my panel to be able to do it as well.
You need to just check the minimums and maximums of your starting point versus your mousemoving point. The problem with the code is you are using the control location as a starting point, but if you move the mouse from bottom-right to top-left, your location needs to change. A control can't have a negative size.
Here is how I re-wrote it (I removed unnecessary stuff for testing):
public class SelectionTool : Panel {
Form parent;
Point _StartingPoint;
public SelectionTool(Form parent, Point startingPoint) {
this.DoubleBuffered = true;
this.Location = startingPoint;
//this.endingPoint = startingPoint;
_StartingPoint = startingPoint;
this.parent = parent;
this.parent.Controls.Add(this);
this.parent.MouseMove += new MouseEventHandler(parent_MouseMove);
this.BringToFront();
this.Size = new Size(0, 0);
}
public void parent_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
int minX = Math.Min(e.Location.X, _StartingPoint.X);
int minY = Math.Min(e.Location.Y, _StartingPoint.Y);
int maxX = Math.Max(e.Location.X, _StartingPoint.X);
int maxY = Math.Max(e.Location.Y, _StartingPoint.Y);
this.SetBounds(minX, minY, maxX - minX, maxY - minY);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
this.BackColor = Color.Blue;
}
}
Here is the code I used to test it on a form:
private SelectionTool _SelectPanel = null;
private void Form1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
if (_SelectPanel == null)
_SelectPanel = new SelectionTool(this, e.Location);
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e) {
if (_SelectPanel != null) {
_SelectPanel.Dispose();
_SelectPanel = null;
}
}