0

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); 
} 

}

Community
  • 1
  • 1
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862

1 Answers1

1

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);
}
John Arlen
  • 6,539
  • 2
  • 33
  • 42
  • Trying the example here: http://www.switchonthecode.com/tutorials/winforms-painting-on-top-of-child-controls ...I get, "System.StackOverflowException was unhandled in the MouseMove() event." When I comment out all the "base."s, it no longer gives that err msg, BUT it also doesn't work (left mouse button, drag, mouseup displays no rectangle) – B. Clay Shannon-B. Crow Raven Apr 29 '12 at 22:16
  • Thanks, however, I get the Stack Overflow with your sample, too - with nothing else at all added, and nothing on the form - as bare bones as can be (less your code): System.StackOverflowException was unhandled – B. Clay Shannon-B. Crow Raven Apr 29 '12 at 23:53
  • Please note my update to my original post: with the calls to base.* commented out, your code *almost* works (if only it would "forget" previous rectangles drawn). – B. Clay Shannon-B. Crow Raven Apr 29 '12 at 23:59
  • DrawReversibleFrame requires a second call with the exact same bounds - to un-draw itself. Two calls in a row results in nothing displayed (no rect) – John Arlen Apr 30 '12 at 01:28
  • @Clay Shannon: You are using events (form.MouseMove += ... ) In this case base calls WILL cause overflow and are unneeded. But wherever possible, it's better to use overrides of base-class methods. – John Arlen Apr 30 '12 at 01:31
  • @JA: Thanks, I (obviously, perhaps) didn't notice that your methods were protected and overridden. I was able to replace my code with your *real* code and avoid StackOverflows. However, I still have the behavior where all previously displayed rectangles are "resurrected" on subsequent MouseDown/drag operations. – B. Clay Shannon-B. Crow Raven Apr 30 '12 at 04:36
  • The MouseUp event "canceling out" the drawing on the MouseMove event works. How can I, though, give the form a form of Alzheimer's so that it forgets previous rectangles drawn upon its surface? Is there an "UndrawReversibleFrame" or some such? – B. Clay Shannon-B. Crow Raven Apr 30 '12 at 04:43
  • @ClayShannon: I'd left out a call in MouseMove. Check that. – John Arlen Apr 30 '12 at 05:09
  • That change actually makes it worse; all of the intermediate calls to MouseMove draw a persistent rectangle, too. So I'll have 8 or 10 rectangles within rectangles that do not even temporarily "go away" with the MouseUp event. – B. Clay Shannon-B. Crow Raven Apr 30 '12 at 14:10
  • Make sure you understand the concept that DrawReversibleFrame is erased by repeating the call with the same bounds. – John Arlen Apr 30 '12 at 16:31
  • Okay, so MouseMove() attempts to draw if the mouse is down - in that case, it erases the previous drawing operation by calling ReverseFrame() and then draws another rectangle by calling DrawFrame(); finally MouseUp erases the last drawing by calling ReverseFrame(). Now that I really understand the code, it seems perfect - it has everything it needs, but nothing superfluous. And theoretically, the code above should work; but it doesn't (for me, anyway). It's as if it needs some kind of call to Invalidate() or something like that to clear out of memory the residual drawing operations. – B. Clay Shannon-B. Crow Raven Apr 30 '12 at 21:52
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10723/discussion-between-john-arlen-and-clay-shannon) – John Arlen Apr 30 '12 at 23:51