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