Capturing the Mouse
In
order to allow the user to drag something, you need to keep track of whether the
mouse is "down" or "up".
It is "down"
from the MouseDown
event to the subsequent MouseUp event.
What
if the user begins to drag something in your window, but then moves the mouse
off of your window while the mouse is down, and release
the mouse in some other window? As far
as we have so far discussed, you would not get the MouseUp event—it would go to that other
window. Unless we’re talking about
programming a drag-and-drop operation, that would not be what you want. Normally, you would want to receive any mouse
messages from the mouse between the MouseDown and the subsequent MouseUp. This is called capturing the mouse. It is
not automatic in Windows itself, but the Foundation Class Libraries do
provide it for you automatically (unlike
MFC or the Win API).
Explicitly: Every MouseDown event (except those corresponding to the second
click of a double-click)
automatically captures subsequent mouse input until there is a MouseUp event
involving the same button.
You
can test for whether the “mouse is down” by checking the Capture property of your form.
Technically, Capture is a boolean member of the Control class, but Form is derived from Control,
so your form has a Capture member.
Dragging Example
Make
a new Visual C# Windows Application called DragTest. We want to write code that outlines a red
rectangle with one corner at (50,50), and lets the
user drag the diagonally opposite corner with the mouse. This is a useful thing to do because it is
the basic interface that allows the user to select a rectangle.

To do
this we start by adding two member variables of type Point:
private Point m_theAnchor; // this will always be (50,50)
private Point m_CurrentPoint;
These
variables are initialized in the form constructor with new Point();
The
reason that we use Point variables
instead of Rectangle variables is
that a rectangle is not allowed to have a negative width or height. This will make it hard to program when the
user drags the mouse above and to the left of the “anchor” point (50,50). In MFC or the
Windows API, rectangles are specified by any two points (for diagonally
opposite corners), so the “left” field could actually be greater than the
“right” field of a rectangle, which was convenient in some situations, like
this one, but made it easy to make mistakes.
In the FCL, you can’t make those mistakes, and you still can program
examples like this one, as we’ll see.
Handle
the Paint event and write Form1_Paint so that it outlines the rectangle:
private void
Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs
e)
{ // make a
rectangle with m_theAnchor and m_CurrentPoint
as
// two diagonally
opposite corners
int left = Math.Min(m_theAnchor.X, m_CurrentPoint.X);
int width = Math.Abs(m_theAnchor.X - m_CurrentPoint.X);
int top = Math.Min(m_theAnchor.Y, m_CurrentPoint.Y);
int height = Math.Abs(m_theAnchor.Y - m_CurrentPoint.Y);
Rectangle r = new
Rectangle(left,top,width,height);
Pen p = new Pen(Color.Red);
e.Graphics.DrawRectangle(p,r);
}
Now
create a handler for each of the three events MouseDown, MouseUp, and MouseMove. The following code updates
m_CurrentPoint at each MouseMove that occurs while the
mouse is down, and
at each MouseUp.
private void
Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{ if(e.Button
!= MouseButtons.Left)
return;
// FCL automatically sets the Capture
property to true before calling this method.
m_theAnchor.X = e.X;
m_theAnchor.Y = e.Y;
m_CurrentPoint = m_theAnchor;
}
private void
Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{ if(e.Button
!= MouseButtons.Left)
return;
if(!Capture)
return; // we’re not dragging. The mouse is moving while the button is up.
m_CurrentPoint.X = e.X;
m_CurrentPoint.Y = e.Y;
Invalidate();
}
private void
Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{ if(e.Button != MouseButtons.Left)
return; // don’t
check Capture property, it’s already been set to false.
m_CurrentPoint.X =
e.X;
m_CurrentPoint.Y =
e.Y;
Invalidate();
}
Dragging using a Child
Window
Often
it is convenient to put whatever it is you want to drag into a small window of
its own, and let Windows take care of the details of redrawing that window in a
new location. The underlying function in
the Win32 API is called MoveWindow. It makes use of the hardware on your graphics
card to copy a rectangle from one place to another during the “vertical retrace
interval”, so that your program doesn’t cause flicker. In the FCL, this function is invisibly invoked
when you set the Location property of
a window. The plan for this program is
roughly as follows: create a child
window, make it
handle MouseDown, MouseUp, and MouseMove, and in MouseMove, if the Capture
property is true (indicating that we’re dragging), change the Location property of the child window.
How
do we create a child window? The easiest
way is to use Visual Studio’s Toolbox. If you don’t see the Toolbox, choose View | Toolbox from the Visual Studio main menu.

If
you don’t see this, maybe it’s because you have your code window on top instead
of your design window. To see the
toolbox as shown you must have your design window on top.
Later
on we will work with all the different “controls” shown in the Toolbox. For now, drag and drop a “PictureBox” from
the toolbox to your form. This is just
a convenient and quick way to create a child window in which we can display an
image. Bring up the property sheet of
your picture box and look for the Image
property. Browse for an image file, for
example, a local copy of the file monalisa.bmp
available on the course website. (First
download the file to your local computer.)
The default size of the picture box is pretty small—resize it with the
mouse on the form design until it just holds the image.

Now
if you build and run your program, you’ll see the image displayed.
Next, add handlers to the
PictureBox for MouseDown, MouseUp, and MouseMove. Be sure that you are adding them to the
PictureBox and not to Form1.
We
will need two member variables of type Point:
private Point m_mouseDownHere, m_lastLocation;
These
should be initialized simply with new Point(). The idea
is that m_lastLocation
is going to be set on MouseDown,
and again at the end of each MouseMove,
so that we can keep track of where the PictureBox was last
displayed. The coordinates of m_lastLocation are in the form’s client
coordinates. But we also need to keep
track of where in the PictureBox the mouse cursor is located. That should stay fixed relative to the
upper-left corner of the PictureBox, as we drag. The variable m_mouseDownHere will give the
location of the cursor in PictureBox client coordinates. That value will stay fixed during the entire
dragging operation. In MouseMove, to
compute the new location, we have to first figure out how much the mouse has
moved since the last display. The mouse
is now at (e.X,
e.Y), where e
is the MouseEventArgs
object, and the
mouse was at m_mouseDownHere.
So the difference of these two points is the vector by which we moved.
If
we add that to m_lastLocation,
we’ll get the new location.
Here’s
the code:
private void
pictureBox1_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{ if(e.Button
!= MouseButtons.Left)
return;
m_lastLocation =
pictureBox1.Location;
// original
location of the window in Parent coordinates
m_mouseDownHere.X =
e.X; // child window coordinates of the mouse
m_mouseDownHere.Y =
e.Y;
}
private void
pictureBox1_MouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{ if(pictureBox1.Capture == false)
return;
pictureBox1.Location = new
Point(m_lastLocation.X + e.X
- m_mouseDownHere.X,
m_lastLocation.Y + e.Y - m_mouseDownHere.Y);
m_lastLocation =
pictureBox1.Location;
}
private void
pictureBox1_MouseUp(object sender,
System.Windows.Forms.MouseEventArgs e)
{ pictureBox1.Location = new
Point(m_lastLocation.X + e.X
- m_mouseDownHere.X,
_lastLocation.Y + e.Y - m_mouseDownHere.Y);
}
This
is all the code it takes! Now you can
drag the Mona Lisa around the screen, and there’s no flicker at all. Observe that it was the PictureBox that
captured the mouse, when you clicked in the PictureBox. That’s why you have to write pictureBox1.Capture in the MouseMove
handler, not just Capture.
Technical Detail:
To set the location, you have
to assign the PictureBox1.Location
property to a new point. You can’t just make assignments to pictureBox.Location.X and pictureBox.Location.Y;
you will get an error message if you try.
Here is the reason: Point is a “value type”, technically a “struct” rather than a “class”. That means that it is “returned by value”,
so that when you access the property pictureBox1.Location
you are actually getting a copy of the point.
In other words, when you write pictureBox1.Location, there is an
implicit function call to a function
that “gets” the value of the Location
property, and that “getter” function returns the location by value. If you then change the .X or .Y fields of
this returned value, of course it has no effect on the actual Location point, which was copied to get
the returned value. This is a somewhat
technical point, but the more details of your programming language you
understand, the better.
To complete your study of
this program, examine
the source code to see what the Design Editor wrote for you to create the
PictureBox: First, it declared a member
variable
private System.Windows.Forms.PictureBox
pictureBox1;
and then it initialized it as follows:
System.Resources.ResourceManager resources = new
System.Resources.ResourceManager(typeof(Form1));
this.pictureBox1
= new System.Windows.Forms.PictureBox();
this.SuspendLayout();
this.pictureBox1.Image
=
((System.Drawing.Image)(resources.GetObject("pictureBox1.Image")));
this.pictureBox1.Location
= new System.Drawing.Point(48, 56);
this.pictureBox1.Name
= "pictureBox1";
this.pictureBox1.Size
= new System.Drawing.Size(100, 152);
this.pictureBox1.TabIndex
= 0;
this.pictureBox1.TabStop
= false;
You can see that the filename
monalisa.bmp hasn’t been mentioned
here. Instead, fetching the actual image
is done by resources.GetObject. You
can verify, though, that the .exe file will run even if it’s placed in a new
folder without the file monalisa.bmp. The image itself has been included in the
.exe file. The word “resources” in
Windows applies to various kinds of data that are not computer code but are
nevertheless packed into the .exe file of a program. Your predecessors, who programmed in the
Win32 API or in MFC,
often had to work much harder to get programs to display
images. Displaying the Mona Lisa here
was no problem at all, just “incidental” to the problem of dragging something.
To finally make the point
that we could be dragging anything
this way, map
the Paint event in the picture
box. Let’s put a caption on the image:
private void
pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Brush b = new SolidBrush(Color.White);
Font f = new Font("Arial",8);
Graphics g = e.Graphics;
g.DrawString("Mona Lisa",f,b,25,85);
}

You wouldn’t actually have
needed to set the Image property of
the picture box. You could have just
left it without an image, and drawn anything you liked in there using GDI+
graphics.