Mouse OnDrag event on GDI graphics object - c#

I have a simple question about mouse events.
I have a WinForms application and I used the GDI+ graphics object
to draw simple shape, a circle.
Now what I want to do is to drag this shape with the mouse.
So when the user is moving the mouse, when the left button is still pressed
I want to move the object.
my question is how to detect if the user is still pressing on the mouse's left button?
I know that there is no onDrag event in winforms.
any ideas?

Check this very simplified example. It doesn't cover many aspects of GDI+ drawing, but gives you an idea how to handle mouse event's in winforms.
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsExamples
{
public partial class DragCircle : Form
{
private bool bDrawCircle;
private int circleX;
private int circleY;
private int circleR = 50;
public DragCircle()
{
InitializeComponent();
}
private void InvalidateCircleRect()
{
this.Invalidate(new Rectangle(circleX, circleY, circleR + 1, circleR + 1));
}
private void DragCircle_MouseDown(object sender, MouseEventArgs e)
{
circleX = e.X;
circleY = e.Y;
bDrawCircle = true;
this.Capture = true;
this.InvalidateCircleRect();
}
private void DragCircle_MouseUp(object sender, MouseEventArgs e)
{
bDrawCircle = false;
this.Capture = false;
this.InvalidateCircleRect();
}
private void DragCircle_MouseMove(object sender, MouseEventArgs e)
{
if (bDrawCircle)
{
this.InvalidateCircleRect(); //Invalidate region that was occupied by circle before move
circleX = e.X;
circleY = e.Y;
this.InvalidateCircleRect(); //Add to invalidate region the rectangle that circle will occupy after move.
}
}
private void DragCircle_Paint(object sender, PaintEventArgs e)
{
if (bDrawCircle)
{
e.Graphics.DrawEllipse(new Pen(Color.Red), circleX, circleY, circleR, circleR);
}
}
}
}

Related

How to detect when holding the mouse down in the mousedown event and then draw pixels randomly around the current mouse cursor location?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;
namespace Random_Pixels
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
pictureBox1.Image = Properties.Resources._image;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = String.Format("X: {0}; Y: {1}", e.X, e.Y);
Bitmap bmp = new Bitmap(pictureBox1.Image);
bmp.SetPixel(e.X, e.Y, Color.Red);
pictureBox1.Image = bmp;
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
}
}
}
now when i'm moving the mouse around it's drawing pixels over the pictureBox1 image.
what i want to add is in the MouseDown event first to detect when holding down the mouse.
then when the mouse is holding down draw many pixels randomly around the current mouse cursor location where it's holding down.
not to fill it around but to draw many pixels around like creating a block of pixels around the mouse cursor. and not rectangle shape just let's call it a "block" of pixels that will be created in randomly positions around the current mouse location.
when the mouse is up in the MouseUp event it will be called a block.
block or call it area of pixels around the current mouse location in random locations but the random location have to be around the current mouse location and not that far from it.
lets decide the block area should be 20,20 around the mouse location something like that i mean that will be the area size around the mouse location to create the random locations of the pixels.
For testing the part of detecting mouse holding down i did this in the mousedown event :
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
heldDown= true;
while(heldDown)
{
Thread.Sleep(500);
progressBar1.Value = progressBar1.Value + 1;
}
}
the problem is that it keep adding percentages to the progressBar1 even if i leave the mouse and in the mouseup event i set the heldDown flag to false.
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
heldDown= false;
}
You don't actually need MouseDown as you can start it from MouseMove, BUT you need to decorate the handler with async so that it will actually allow the MouseUp event to toggle your flag. You'll also use await Task.Delay() instead of Thread.Sleep().
Looks something like:
private async void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
heldDown = true;
while (heldDown)
{
// ... do something in here ...
await Task.Delay(250);
}
}
}
You can get the current cursor position with Cursor.Position, which is in SCREEN coords, and see if it falls within the bounds of the PictureBox. Convert the PictureBox CLIENT coords to screen coords and see if the cursor is within:
private async void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
heldDown = true;
while (heldDown)
{
Point pt = Cursor.Position;
Rectangle rc = pictureBox1.RectangleToScreen(pictureBox1.ClientRectangle);
if (rc.Contains(pt))
{
// ... do something ...
}
await Task.Delay(250);
}
}
}

Method to create a workspace to draw on in C#

I'm new to c#, coming from a python / wxpython background. I'm still very inexperienced.
I'm trying to create a user workspace that allows objects that have a representative shape (box or circle) to be placed. I'd like the user to be able to pan and zoom within the workspace. Eventually I want to have the items have attributes and be able to be connected together with lines.
I created a simple c# app that allows me to paint squares over a tiled .png grid background, but I can't figure out to set up a real workspace to pan or zoom.
At this stage some high level suggestions would be great.
Here is my code just FYI (be kind)
public partial class MainForm : Form
{
bool global_draw_redbox = false;
public MainForm()
{
InitializeComponent();
}
private void redbox(Point mousepos)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
Rectangle myRectangle = new Rectangle(mousepos.X - 125, mousepos.Y - 100, 250, 200);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
if (global_draw_redbox)
{
//Draw the object based on the mouse position
redbox(relativePoint);
//Reset the flag so we draw just one
global_draw_redbox = false;
}
else
{
MessageBox.Show("No object selected");
}
}
private void Form1_MouseDoubleClick(object sender, MouseEventArgs e)
{
MessageBox.Show("Test Mouse Double Click");
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
XY_Mouse_Position.Text = relativePoint.ToString();
}
private void boxToolStripMenuItem_Click(object sender, EventArgs e)
{
global_draw_redbox = true;
}
}

How can I capture mouse events that occur outside of a (WPF) window?

I have a Window element that has WindowStyle="None" and AllowsTransparency="True", therefore it has no title bar and supports transparency.
I want the user to be able to move the Window to any position on the screen by left-clicking anywhere within the Window and dragging. The Window should drag along with the mouse as long as the left mouse button is pressed down.
I was able to get this functionality to work with one exception: when the mouse moves outside of the Window (such as when the left mouse button was pressed down near the edge of the Window and the mouse is moved quicly), the Window no longer captures the mouse position and does not drag along with the mouse.
Here is the code from the code-behind that I use to get the job done:
public Point MouseDownPosition { get; set; }
public Point MousePosition { get; set; }
public bool MouseIsDown { get; set; }
private void window_MyWindowName_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MouseDownPosition = e.GetPosition(null);
MouseIsDown = true;
}
private void window_MyWindowName_MouseMove(object sender, MouseEventArgs e)
{
if (MouseIsDown)
{
MousePosition = e.GetPosition(null);
Left += MousePosition.X - MouseDownPosition.X;
Top += MousePosition.Y - MouseDownPosition.Y;
}
}
private void window_MyWindowName_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MouseIsDown = false;
}
I think you are looking for this: Processing Global Mouse and Keyboard Hooks in C#
Url: Processing Global Mouse and Keyboard Hooks in C#
This class allows you to tap keyboard and mouse and/or to detect their activity even when an application runs in the background or does not have any user interface at all.
This class raises common .NET events with KeyEventArgs and MouseEventArgs, so you can easily retrieve any information you need.
There is an example and explain and demo to use.
Great tutorial!
Example:
UserActivityHook actHook;
void MainFormLoad(object sender, System.EventArgs e)
{
actHook= new UserActivityHook(); // crate an instance
// hang on events
actHook.OnMouseActivity+=new MouseEventHandler(MouseMoved);
actHook.KeyDown+=new KeyEventHandler(MyKeyDown);
actHook.KeyPress+=new KeyPressEventHandler(MyKeyPress);
actHook.KeyUp+=new KeyEventHandler(MyKeyUp);
}
Now, an example of how to process an event:
public void MouseMoved(object sender, MouseEventArgs e)
{
labelMousePosition.Text=String.Format("x={0} y={1}", e.X, e.Y);
if (e.Clicks>0) LogWrite("MouseButton - " + e.Button.ToString());
}
I believe you are reinventing the wheel. Search for "Window.DragMove".
Example:
private void title_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
Try it this way:
// method to convert from 'old' WinForms Point to 'new' WPF Point structure:
public System.Windows.Point ConvertToPoint(System.Drawing.Point p)
{
return new System.Windows.Point(p.X,p.Y);
}
// some locals you will need:
bool mid = false; // Mouse Is Down
int x=0, y=0;
// Mouse down event
private void MainForm_MouseDown(object sender, MouseButtonEventArgs e)
{
mid=true;
Point p = e.GetPosition(this);
x = (int)p.X;
y = (int)p.Y;
}
// Mouse move event
private void MainForm_MouseMove(object sender, MouseButtonEventArgs e)
{
if(mid)
{
int x1 = e.GetPosition(this).X;
int y1 = e.GetPosition(this).Y;
Left = x1-x;
Top = y1-y;
}
}
// Mouse up event
private void MainForm_MouseUp(object sender, MouseButtonEventArgs e)
{
mid = false;
}

2D Array of RectangleShapes

I am developing a very rudimentary drawing program: A 2D grid comprised of multiple RectangleShapes, around 20x30 pixels each, which when clicked change color based on user RGB input, which works just fine:
Color SelectedColor = new Color();
private void Pixel_1_1_Click(object sender, EventArgs e) // on Rectangle click
{
Pixel_1_1.FillColor = SelectedColor; // change to currently desired color.
}
Since the number of squares is rising dramatically, I'm looking for a way to arrange the "pixel" rectangles into a 2D array. (I really don't want to have to make a Pixel_Click method for every single Rectangle on the screen!) Hoping eventually to be able to call something like:
private void Pixel_[x]_[y]_Click(object sender, EventArgs e)
{
Pixel_[x]_[y].FillColor = SelectedColor;
}
My friends suggest the use of an anonymous delegate, but I don't understand how to fully use one to solve my problem.
What would be the best way to generate a 2D array of rectangles in a C# Windows Form? And once generated, how can I access them with a single method for variant values of x and y?
While you are probably correct in thinking of each rectangle as an object, it probably isn't correct to think of each rectangle as a windows control, especially since you have so many of them.
So try creating your own rectangle object:
public class MyRect {
public Color FillColor { get; set; }
public Rectangle Rectangle { get; set; }
public MyRect(Rectangle r, Color c) {
this.Rectangle = r;
this.FillColor = c;
}
}
Now you just need to keep a list of your objects and paint on a single Panel control (or PictureBox) all of your rectangles:
private List<MyRect> myRectangles = new List<MyRect>();
public Form1() {
InitializeComponent();
myRectangles.Add(new MyRect(new Rectangle(10, 10, 64, 16), Color.Blue));
myRectangles.Add(new MyRect(new Rectangle(20, 48, 16, 64), Color.Red));
}
private void panel1_Paint(object sender, PaintEventArgs e) {
foreach (MyRect mr in myRectangles) {
using (SolidBrush sb = new SolidBrush(mr.FillColor)) {
e.Graphics.FillRectangle(sb, mr.Rectangle);
}
}
}
To handle the "click" event of the rectangles, you just handle the MouseDown or MouseClick event of your container control and determine yourself which rectangle is being clicked on:
void panel1_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
foreach (MyRect mr in myRectangles) {
if (mr.Rectangle.Contains(e.Location)) {
ChangeColor(mr, Color.Green);
}
}
panel1.Invalidate();
}
}
private void ChangeColor(MyRect mr, Color newColor) {
mr.FillColor = newColor;
}
If you want to maintain the rectangles as components on screen then you can assign all of them the same click event, the click event will have a little dropdown to pick an existing event. To know which recantangle was clicked use the sender parameter ((Pixel)sender).FillColor = SelectedColor;
For ease I would recommend using something like a panel and drawing rectangles on it, That means you only have a single click event to deal with. So now your question becomes "How do I draw a grid of rectangles on a panel" and "How do I know which rectangle was clicked"
So for the first part you could use this not the very efficient way.
Create a class which stores the information about your pixels
class MyPixel
{
public Color PixelColour;
public Rectangle Bounds;
}
Keep a list of them in memory
List<MyPixels> MyGrid = new List<MyPixels>();
then in the onpaint event of the panel Draw the pixels on the panel
foreach(MyPixel Pixel in MyGrid)
{
using(Brush B = new SolidBrush(Pixel.PixelColor))
{
e.Graphics.DrawRectangle(B, Pixel.Bounds);
}
}
Now in the click event you'll need to know which pixel was clicked
foreach(MyPixel Pixel in MyGrid)
{
if (Pixel.Bounds.Contains(e.Location))
{
PixelClicked(Pixel);
}
}
I believe you're going about this the wrong way. What you want to do is to draw directly into a bitmap. Here is some code that uses a PictureBox to allow the user to draw into it.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Pen _pen;
private bool _mouseDown;
private int _startX;
private int _startY;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
_pen = new Pen(Color.Black);
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
_mouseDown = true;
_startX = e.X;
_startY = e.Y;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
_mouseDown = false;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (_mouseDown)
{
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawLine(_pen, _startX, _startY, e.X, e.Y);
_startX = e.X;
_startY = e.Y;
}
pictureBox1.Invalidate();
}
}
}
}
This is the normal method to write a painting app and is quite performant as well. You can also easily save, write new tools or manipulate images better in this way.

How do I draw a rectangle based on the movement of the mouse?

I found sample code here for drawing on a form:
http://msdn.microsoft.com/en-us/library/aa287522(v=vs.71).aspx
As a followup to this requirement (discovering which controls are beneath a rectangle described by
the user dragging the mouse):
There seems to be a mismatch between the location of my controls and the location of my MouseDown and -Up events
...I want to provide the user instant/constant feedback about just what they are about to select
(when/if they release the mouse button). I want to not just draw a line following the mouse's
movement, but draw the rectangle that is being described by their mousewrangling efforts.
I'm thinking the MouseMove event, coupled with code from the two links above, could do the trick, but is that fired too often/would that have a malevolent impact on performance? If so, what would be a preferable event to hook, or would a timer be the way to go here?
UPDATE
This code, adapted from John's example below (the only difference is the StackOverflow-inducing calls to base.* are commented out, and I changed the color from red to black (no reference to Stendahl intended)), works except that previously drawn rectangles display again after releasing the mouse. IOW, the first rectangle draws perfectly - it disappears with the mouse up click (as intended). However, when I describe a second rectangle by depressing the left mouse key and dragging down and to the right, the first rectangle displays again! And this continues to happen - every previously drawn rectangle is remembered and brought back to the fore when a new rectangle is being drawn.
public partial class Form1 : Form
{
private Point? _start;
private Rectangle _previousBounds;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
_start = e.Location;
//base.OnMouseDown(e);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (_start.HasValue)
DrawFrame(e.Location);
//base.OnMouseMove(e);
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
ReverseFrame();
_start = null;
//base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
}
ControlPaint.DrawReversibleFrame() will do what you want. Performance is not generally a problem - just keep it small and clean.
--
EDIT: Added a code sample. StackOverflowException indicates something is wrong - but without seeing yours, can't answer directly.
private Point? _start;
private Rectangle _previousBounds;
protected override void OnMouseDown(MouseEventArgs e)
{
_start = e.Location;
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if( _start.HasValue ) {
ReverseFrame();
DrawFrame(e.Location);
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
ReverseFrame();
_start = null;
base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}

Categories

Resources