Mouse Dragging and drawing a rectangle

R

RobinS

I am drawing a rectangle on a picture that has already been drawn
on the graphics area (a user control). It works something like this:

//in the MouseDown event
m_isDragging = true;
m_oldX = e.X; //save original positions
m_oldY = e.Y;

//in the MouseMove event
if (m_isDragging)
{
m_newX = e.X;
m_newY = e.Y;
int firstX = (m_newX < m_oldX) ? m_newX : m_oldX;
int firstY = (m_newY < m_oldY) ? m_newY : m_oldY;
m_diffX = Math.Abs(m_oldX - m_newX);
m_diffY = Math.Abs(m_oldY - m_newY);
Graphics g = Graphics.FromHwnd(this.Handle);
Rectangle myRectangle =
new Rectangle(firstX, firstY, m_diffX, m_diffY);
g.DrawRectangle(m_myPen, myRectangle);
g.Dispose();
Invalidate(myRectangle);
}

//in the MouseUp event
if (m_isDragging)
{
m_isDragging = false;
//save the position and size of the rectangle (code snipped)

//invalidate the whole usercontrol (which is just the panel
// on which the picture was already painted)
this.Invalidate();

}

This works great when you keep drawing the rectangle bigger.

But if you backtrack while dragging, it leaves the remnants
of the rectangles you have already drawn behind.

How do I handle that?

I've tried saving thd old rectangle and invalidating that
when drawing the new rectangle, like this:

Rectangle m_oldRectangle;
//in the MouseMove event
if (m_isDragging)
{
m_newX = e.X;
m_newY = e.Y;
int firstX = (m_newX < m_oldX) ? m_newX : m_oldX;
int firstY = (m_newY < m_oldY) ? m_newY : m_oldY;
m_diffX = Math.Abs(m_oldX - m_newX);
m_diffY = Math.Abs(m_oldY - m_newY);
Graphics g = Graphics.FromHwnd(this.Handle);
Rectangle myRectangle =
new Rectangle(firstX, firstY, m_diffX, m_diffY);

g.DrawRectangle(m_myPen, myRectangle);
g.Dispose();

//I'd probably want to use whichever rectangle is larger here,
// but I'm just trying this to see what it does.
Invalidate(m_oldRectangle); //instead of myRectangle

//save the old rectangle
m_oldRectangle =
new Rectangle(firstX, firstY, m_diffX, m_diffY);
}

This doesn't work either.

Any other ideas? I'd appreciate any help you can provide.

Thanks in advance,
Robin S.
 
P

Peter Duniho

RobinS said:
This works great when you keep drawing the rectangle bigger.

But if you backtrack while dragging, it leaves the remnants
of the rectangles you have already drawn behind.

How do I handle that?

The biggest thing wrong that I see with your code is that you are
drawing the rectangle in the MouseMove event handler. You should only
be drawing anything, selection rectangle included, in your Paint event
handler. (replace "XXX event handler" with "OnXXX override method" as
appropriate :) ).

The second version of MouseMove handling you posted appears to me that
it would have the opposite problem, erasing the newly-drawn rectangle
just after you've drawn it. Sticking as closely to the code you've
posted, I think this would be better:

Rectangle m_oldRectangle;
//in the MouseMove event
if (m_isDragging)
{
m_newX = e.X;
m_newY = e.Y;
int firstX = (m_newX < m_oldX) ? m_newX : m_oldX;
int firstY = (m_newY < m_oldY) ? m_newY : m_oldY;
m_diffX = Math.Abs(m_oldX - m_newX);
m_diffY = Math.Abs(m_oldY - m_newY);
Rectangle myRectangle =
new Rectangle(firstX, firstY, m_diffX, m_diffY);

// Invalidate the combined rectangles, to ensure that
// the old rectangle is erased and the new one is drawn
Invalidate(Rectangle.Union(m_oldRectangle, myRectangle));

//save the old rectangle
m_oldRectangle = myRectangle;
}

Then in the Paint event:

e.Graphics.DrawRectangle(m_myPen, m_oldRectangle);

Of course, you probably would want to rename m_oldRectangle so that it
more accurate reflects the "current" nature of the rectangle. :)

Pete
 
R

RobinS

Peter Duniho said:
The biggest thing wrong that I see with your code is that you are drawing
the rectangle in the MouseMove event handler. You should only be drawing
anything, selection rectangle included, in your Paint event handler.
(replace "XXX event handler" with "OnXXX override method" as appropriate
:) ).

The second version of MouseMove handling you posted appears to me that it
would have the opposite problem, erasing the newly-drawn rectangle just
after you've drawn it. Sticking as closely to the code you've posted, I
think this would be better:

Rectangle m_oldRectangle;
//in the MouseMove event
if (m_isDragging)
{
m_newX = e.X;
m_newY = e.Y;
int firstX = (m_newX < m_oldX) ? m_newX : m_oldX;
int firstY = (m_newY < m_oldY) ? m_newY : m_oldY;
m_diffX = Math.Abs(m_oldX - m_newX);
m_diffY = Math.Abs(m_oldY - m_newY);
Rectangle myRectangle =
new Rectangle(firstX, firstY, m_diffX, m_diffY);

// Invalidate the combined rectangles, to ensure that
// the old rectangle is erased and the new one is drawn
Invalidate(Rectangle.Union(m_oldRectangle, myRectangle));

//save the old rectangle
m_oldRectangle = myRectangle;
}

Then in the Paint event:

e.Graphics.DrawRectangle(m_myPen, m_oldRectangle);

Of course, you probably would want to rename m_oldRectangle so that it
more accurate reflects the "current" nature of the rectangle. :)

Pete

Thanks for your quick response.

1) The events are actual events. I just didn't want to put the
signature in there because I was re-typing rather than copying
and pasting. I keep trying to drag text over from my desktop
(work) machine to my laptop (personal) machine, but it just won't
go past the edge of the desktop, dang it.

2) Ah, the paint event. So here's the deal. I have a picture,
and I have a bunch of attributes that are applied to the picture.
(This is all part of an image editor.) So the user can add text,
rotate, draw rectangles, crop, etc. All of those attributes are
stored, and when the picture is displayed, there is a routine
called by the Paint event that loads the picture, applies the
attributes, and returns a finished bitmap, which is displayed
by the Paint event. In this way, we can allow the user to go
back and edit the attributes w/o losing any clarity or anything
else about the original picture.

The MouseDown draws the rectangle "temporarily". After the user
lets go of the mouse, the panel is invalidated, invoking the
Paint event, which calls the routine to load the picture
and apply the attributes, one of which is the newly stored
rectangle (storing X, Y, width, and height % in case they
change the size of the image).

All of that is working great. I'm just having trouble
with the interim MouseMove drawing of the rectangle on the
picture.

Despite what you think it might do, the code in my original
post works except when you reverse direction when drawing
the rectangle.

I tried changing Invalidate(myRectangle)
or Invalidate(oldRectangle)
to
Invalidate(Rectangle.Union(m_oldRectangle, m_Rectangle));
but it didn't help.

Any other ideas? Is there a way to invalidate just that area
so the entire thing doesn't repaint? Am I really going to
have to rewrite my Paint event to handle this, and how would
I invoke it from MouseMove? I don't really want to invalidate
the whole panel if I can help it.

Thx,
Robin S.
 
R

RobinS

RobinS said:
Thanks for your quick response.

1) The events are actual events. I just didn't want to put the
signature in there because I was re-typing rather than copying
and pasting. I keep trying to drag text over from my desktop
(work) machine to my laptop (personal) machine, but it just won't
go past the edge of the desktop, dang it.

2) Ah, the paint event. So here's the deal. I have a picture,
and I have a bunch of attributes that are applied to the picture.
(This is all part of an image editor.) So the user can add text,
rotate, draw rectangles, crop, etc. All of those attributes are
stored, and when the picture is displayed, there is a routine
called by the Paint event that loads the picture, applies the
attributes, and returns a finished bitmap, which is displayed
by the Paint event. In this way, we can allow the user to go
back and edit the attributes w/o losing any clarity or anything
else about the original picture.

The MouseDown draws the rectangle "temporarily". After the user
lets go of the mouse, the panel is invalidated, invoking the
Paint event, which calls the routine to load the picture
and apply the attributes, one of which is the newly stored
rectangle (storing X, Y, width, and height % in case they
change the size of the image).

All of that is working great. I'm just having trouble
with the interim MouseMove drawing of the rectangle on the
picture.

Despite what you think it might do, the code in my original
post works except when you reverse direction when drawing
the rectangle.

I tried changing Invalidate(myRectangle)
or Invalidate(oldRectangle)
to
Invalidate(Rectangle.Union(m_oldRectangle, m_Rectangle));
but it didn't help.

Any other ideas? Is there a way to invalidate just that area
so the entire thing doesn't repaint? Am I really going to
have to rewrite my Paint event to handle this, and how would
I invoke it from MouseMove? I don't really want to invalidate
the whole panel if I can help it.

Thx,
Robin S.

I tried out what you recommended. If I add it to the paint event,
I'm going to have to repaint the picture under the rectangle
every time, because if I don't, it only shows a black square.

I believe this will make it difficult (if not impossible) for
the user to draw a rectangle around something on the picture. ;-)

I'm afraid if I repaint the entire image as they drag, it will
flicker, and I suspect I did that first and then changed it to
do this. This is a long-standing bug that I have managed to
avoid fixing for a couple of months, but now I've fixed all
the other ones, and have only this one (that I know of) left.

Thanks,
Robin S.
 
P

Peter Duniho

RobinS said:
I tried out what you recommended. If I add it to the paint event,
I'm going to have to repaint the picture under the rectangle
every time, because if I don't, it only shows a black square.

I believe this will make it difficult (if not impossible) for
the user to draw a rectangle around something on the picture. ;-)

I am sorry you don't feel my advice is useful. Suffice to say, it is
required if you intend for your application to work as a normal Windows
application.

Yes, this means that in your Paint event handler, you need to redraw
everything. That's what the Paint event handler does. There are
optimizations you can make, when necessary, to speed things up. But you
still must draw everything.
I'm afraid if I repaint the entire image as they drag, it will
flicker, and I suspect I did that first and then changed it to
do this. This is a long-standing bug that I have managed to
avoid fixing for a couple of months, but now I've fixed all
the other ones, and have only this one (that I know of) left.

Flickering is a common consequence of the basic Windows drawing model.
It is addressed by enabled double-buffering.

None of this changes the fact that drawing your selection rectangle in
the mouse event handler is the wrong thing to do. All drawing must
occur in the Paint event in order for things to work correctly.

Pete
 
P

pedrito

Peter Duniho said:
I am sorry you don't feel my advice is useful. Suffice to say, it is
required if you intend for your application to work as a normal Windows
application.

Yes, this means that in your Paint event handler, you need to redraw
everything. That's what the Paint event handler does. There are
optimizations you can make, when necessary, to speed things up. But you
still must draw everything.


Flickering is a common consequence of the basic Windows drawing model. It
is addressed by enabled double-buffering.

None of this changes the fact that drawing your selection rectangle in the
mouse event handler is the wrong thing to do. All drawing must occur in
the Paint event in order for things to work correctly.

Pete

Pete is 100% on the nose. This is precisely how it should be done. You can
do it however you want, but if you want it to work, you'll follow his
advice. You might even get it to work for you using your method. But I can
almost guarantee you, it's not going to work for some people. There video
driver is going to act differently or their CPU will be slower, and stuff
that works just fine for you is going to be leaving behind trails and
artifacts and other nicities on their machine.

Or even try this: What happens when you drag stuff on top of your window, or
when you've got an app with the Always on Top attribute partially obscuring
your app? What about split across multiple monitors

You're just drawing a bitmaps and some stuff. Think about a grid control
that has a bunch of cells. Each of those cells might have text or a checkbox
or an image or whatever in it. Even though their contents are completely
different, when a grid control draws, it draws EVERYTHING in the paint
event. When you select a cell, you make a note of it in the mouse event, but
you paint the selection in the paint event. You don't paint the cell when
they actually click on it.

Flickering is avoided by seting the double buffer style as Pete mentioned.
If your code is moderately efficient, and maybe with the help of a profiler,
like nProf, you can get it that way, there shouldn't be any serious
performance problems. Do it any other way, and I guarantee you'll see
problems down the road.
 
R

RobinS

Peter Duniho said:
I am sorry you don't feel my advice is useful. Suffice to say, it is
required if you intend for your application to work as a normal Windows
application.

Yes, this means that in your Paint event handler, you need to redraw
everything. That's what the Paint event handler does. There are
optimizations you can make, when necessary, to speed things up. But you
still must draw everything.


Flickering is a common consequence of the basic Windows drawing model. It
is addressed by enabled double-buffering.

None of this changes the fact that drawing your selection rectangle in
the mouse event handler is the wrong thing to do. All drawing must occur
in the Paint event in order for things to work correctly.

Pete

Thanks for the advice, Pete. I think back when I first started
working on this, I had some problems with the drawing of the
images and some major flickering and redrawing. I think I
started out with everything in the Paint event, because there
is some residual code in there handling the paint event when
someone is dragging the mouse across the surface.

So I did some cleanup and added some checking so it doesn't
reload the image from disk, but keeps it in memory and
just repaints it as they draw the rectangle. Then I stuck the
DrawRectangle in the paint event (if (m_IsDragging)) as you
recommended, changed the routine that handled the rectangle
to invalide the panel, and this fixed my problem.

Many thanks for your kind and helpful advice.

Robin S.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

simple drawing with a mouse 8
A Little Help?? 2

Top