1

My usage of the DrawReversibleFrame() method results in drawing an offset and reversed shadow of my dragging operation. The name of the method (DrawReversibleFrame()) seems to indicate that perhaps this is the way it's supposed to work (draw a reverse image, rather than faithfully mimic your movements). However, I want the opposite (and there seems to be no simple DrawFrame() method).

I tried two examples that were indicated after I posted the initial question: How do I draw a rectangle based on the movement of the mouse?

These two attempts can be found here:

filled rubber band in Winforms Application

..and here:

http://www.switchonthecode.com/tutorials/winforms-painting-on-top-of-child-controls

...but neither one works for me.

First of all, the calls to base.* in the events of both examples cause StackOverflows (no pun intended) when I run them (often I don't even need to do anything for the StackOverflows to blow up my app).

I am able to get code adapted from the DrawReversibleFrame() example (the second link above) ALMOST working. However, when I drag to the right and down (from top left to bottom righty) with the mouse, the rectangle drawn starts from the bottom right and goes up to the top left (but I'm dragging the opposite way -- from the top left to the bottom right) -- the size/amount moved seems precisely right, though.

Moreover, the point at which the draw operation begin is not in exactly the right spot, either -- it starts a little to the left of and above my initial point.

Note: I'm using PointToScreen() because the dragging operation is taking place on a TableLayoutPanel, which has been placed on top of the form. If I don't use it, I see no drawing at all take place.

My code is:

public partial class Form1 : Form
{
    private bool _IsSelecting = false;
    private Point _StartPoint = Point.Empty;
    private Rectangle _FrameRect = Rectangle.Empty; //1

    public Form1()
    {
        InitializeComponent();
        // DoubleBuffered = true; //makes no apparent difference either way
    }

    private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Left)
            return;

        _IsSelecting = true;
        _StartPoint = PointToScreen(e.Location);
        _FrameRect = new Rectangle(_StartPoint, Size.Empty);
    }

    private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
    {
        if (!_IsSelecting)
            return;

        ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);

        Point pt = PointToScreen(e.Location);
        _FrameRect.Width = _StartPoint.X - pt.X;
        _FrameRect.Height = _StartPoint.Y - pt.Y;

        ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);
    }

    private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
    {
        if (!_IsSelecting)
            return;

        ControlPaint.DrawReversibleFrame(_FrameRect,
            Color.Black, FrameStyle.Dashed);

        _IsSelecting = false;
        _FrameRect = Rectangle.Empty;
        _StartPoint = Point.Empty;
    }
}

UPDATE

In response to John's comment, I reversed the math here:

    _FrameRect.Width = pt.X - _StartPoint.X;
    _FrameRect.Height = pt.Y - _StartPoint.Y;

...but that only teases me, as it makes it so that it almost works, but changing the calls to PointToScreen() to PointToClient() make it worse (further to the left and top, and sometimes way too large of a rectangle).

Here's the code as it is now (rectangle is the perfect size, but starts to the left and above the mouse):

private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left)
        return;

    _IsSelecting = true;
    _StartPoint = PointToScreen(e.Location);
    //_StartPoint = PointToClient(e.Location); //<- this makes it way worse - the rectangle is way too big, and starts even further up and to the left
    _FrameRect = new Rectangle(_StartPoint, Size.Empty);
}

private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
{
    if (!_IsSelecting)
        return;

    ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);
    Point pt = PointToScreen(e.Location);
    //Point pt = PointToClient(e.Location); //worse
    _FrameRect.Width = pt.X - _StartPoint.X;
    _FrameRect.Height = pt.Y - _StartPoint.Y;
    ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);
}

private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
{
    if (!_IsSelecting)
        return;

    ControlPaint.DrawReversibleFrame(_FrameRect,
        Color.Black, FrameStyle.Dashed);
    _IsSelecting = false;
    _FrameRect = Rectangle.Empty;
    _StartPoint = Point.Empty;
}

ANOTHER UPDATE

Note: I'm performing the mousedown on a dynamically-created TableLayoutPanel, which sits on a panel, which sits on the form.

When I added the code below, the values were 25, then 146. So it seems like to should work - it's compensating for the fact that there are things above the TableLayoutPanel when it converts the Y value to 146. But it's not enough, or...???

log.Debug(String.Format("Mouse Down Location.Y = {0}", e.Location.Y));
_StartPoint = PointToScreen(e.Location);
log.Debug(String.Format("Mouse Down PointToScreen Location.Y = {0}",    _StartPoint.Y));

YET ANOTHER UPDATE

I was finally able to discover TextBoxes beneath the rectangle with answers to my question here (I wasn't able to post a new question here on StackOverflow, as I had exceeded my 50-questions-in-30-days limit):

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/9039ab0c-a587-476c-a952-9f35c6ccba42/

...but now I have another problem:

The code (below) does find controls beneath it now, because it loops through all of the TextBoxes on the panel, and it does discover the correct number of controls beneath the rectangle drawn (if I drag over nine TextBoxes, it finds nine).

HOWEVER, the TextBoxes tagged as those found are not the right ones - they are not the ones over which I have dragged, but are rather located several rows down in the TableLayoutPanel!

The _FrameRect drawn is converted from PointToScreen(), and the textBox.Bounds are converted from RectangleToScreen(), so they should be in synch, yet somehow they are not... (if I don't have these conversions of absolute location to relative location, no textBoxes are discovered at all).

private void HighlightAllTextBoxValsBetweenPoints() {
    foreach (Control ctrl in tableLayoutPanelGreatgooglyMoogly.Controls) {
        var tb = ctrl as TextBox;
        if (tb == null)
            continue;
        if (RectangleToScreen(tb.Bounds).IntersectsWith(_FrameRect)) {
            //MessageBox.Show(String.Format("{0}", tb.Name));
            tb.BackColor = PSEUDO_HIGHLIGHT_COLOR;
        }
    }
}

Again, the right number of controls are found, and they are "highlighted" (backColor property changed), but they are not the right ones.

FINAL UPDATE

Based on the code a cat (or a frogleg, I guess) uploaded here:

http://www.c-sharpcorner.com/Forums/Thread/170813/

...I was able to get the pseudo-highlighting of the controls working. Here's the pertinent code:

// Declare a rectangle and a point:
Rectangle describedRect;
Point startingPoint;
private bool _IsSelecting = false; // <- already had this

// In the MouseDown event:
_IsSelecting = true;
startingPoint = e.Location;

// In the MouseMove event:
if (!_IsSelecting)
    return;
describedRect = Rectangle.FromLTRB(startingPoint.X, startingPoint.Y, e.X, e.Y); 

// In the MouseUp event:
if (!_IsSelecting) 
    return;
HighlightAllTextBoxValsBetweenPoints();
_IsSelecting = false;

private void HighlightAllTextBoxValsBetweenPoints() {
    foreach (Control ctrl in tableLayoutPanelGreatGooglyMoogly.Controls) {
        var tb = ctrl as TextBox;
        if (tb == null)
            continue;

        if (describedRect.IntersectsWith(tb.Bounds)) {
            tb.BackColor = PSEUDO_HIGHLIGHT_COLOR;
        }
    }
}
Community
  • 1
  • 1
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862

1 Answers1

1

You need to use the PointToScreen method of the TableLayoutPanel control you are trying to use:

private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e) {
  if (e.Button != MouseButtons.Left)
    return;

  _IsSelecting = true;
  _StartPoint = tableLayoutPanel1.PointToScreen(e.Location);
  _FrameRect = new Rectangle(_StartPoint, Size.Empty);
}

private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e) {
  if (!_IsSelecting)
    return;

  ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);    
  Point pt = tableLayoutPanel1.PointToScreen(e.Location);
  _FrameRect.Width = pt.X - _StartPoint.X;
  _FrameRect.Height = pt.Y - _StartPoint.Y;
  ControlPaint.DrawReversibleFrame(_FrameRect, Color.Black, FrameStyle.Dashed);
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • I've tried that and, like so many other examples, it *almost* works - but the rectangle is drawn too far up (the "Y" value is too low). The "X" is perfect. – B. Clay Shannon-B. Crow Raven May 01 '12 at 20:18
  • @ClayShannon Oops, my bad. Updated code. Just specify the control that should convert the `PointToScreen` variable, which in your case, would be `tableLayoutPanel1`. – LarsTech May 01 '12 at 20:26
  • I can see that the difference that Location.Y is "off" (too high) is the height of the section above my panel. IOW, if the top panel is 3 inches tall, and I press the mouse down inch below the middle panel, the rectangle is drawn one inch below the top panel. But to what do I need to add the height of the top panel to get the rectangle to draw correctly? Offset seems to make no difference whatsoever. Neither does this: _StartPoint.Y = _StartPoint.Y + panelTop.Size.Height; – B. Clay Shannon-B. Crow Raven May 01 '12 at 20:44
  • perfect, that works (prepending tableLayoutPanel1 to the call to PointToScreen)! – B. Clay Shannon-B. Crow Raven May 01 '12 at 20:47
  • HOWEVER (see my last Update in my original post) – B. Clay Shannon-B. Crow Raven May 02 '12 at 16:47
  • Finally got it solved. It seems all that convoluted conversion using PointToScreen(), ScreenToForm(), WhenByNow, TreeByLeaf, AllByAll, DeepByDeep, etc. is unnecessary. I'm posting the "fix" as the FINAL UPDATE, appended to the original question. – B. Clay Shannon-B. Crow Raven May 02 '12 at 22:17