Reputation: 5294
I'm using DrawReversibleFrame to draw a rectangle with inverse colors based on mouse position over a control called SelectionTest. The basic code to use DrawReversibleFrame was taken from this MSDN sample.
If the control is redrawn (after an invalidate or refresh) during the area selecting, the rectangle can disappear or have multiples copies with different sizes. You may ask me, why you are Refreshing the control? Actually, I'm not! The control Paint event is also not raised.
I found out that the redraw only happens when the rectangle is drawn in between two other controls that Invalidates really fast (eg: on mouse move event or by a timer).
To reproduce this topic isue, I've created two sample Classes:
SelectionTest, handles the DrawReversibleFrame. The checkbox This.Invalidate timer, enables/disables a timer that calls Invalidate of the SelectionTest. If you activate it, you can easyl reproduce the unwanted DrawReversibleFrame behavior. Please notice that this (Invalidade call) is just a "cheat" to reproduce the bug. On my app I'm not calling Invalidate or asking the control to be redrawn in any way. As I said, on my app the Paint event is not raised.
Form1, owns the SelectionTest in between two panels. It owns two main checkboxes, that control the Invalidate on mouse move and invalidate timer. If both are selected, the unwanted behavior is more notable.
Here is a screenshot of the unwanted behavior. Note the selections on the top right corner of the center (darker region) control:
Questions:
SelectionTest.cs - Control that uses the DrawReversibleFrame based on the MSDN sample.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace DrawReversibleFrameTest
{
public partial class SelectionTest : UserControl
{
Timer timer = new Timer();
public SelectionTest()
{
InitializeComponent();
this.MouseDown += SelectionTest_MouseDown;
this.MouseMove += SelectionTest_MouseMove;
this.MouseUp += SelectionTest_MouseUp;
timer.Interval = 100;
timer.Tick += timer_Tick;
timer.Enabled = ckbTimerEnabled.Checked;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Console.WriteLine("SelectionTest OnPaint");
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Console.WriteLine("SelectionTest OnPaintBackground");
}
void timer_Tick(object sender, EventArgs e)
{
this.Invalidate();
}
// The following three methods will draw a rectangle and allow
// the user to use the mouse to resize the rectangle. If the
// rectangle intersects a control's client rectangle, the
// control's color will change.
bool isDrag = false;
Rectangle theRectangle = new Rectangle(new Point(0, 0), new Size(0, 0));
Point startPoint;
private void SelectionTest_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Set the isDrag variable to true and get the starting point
// by using the PointToScreen method to convert form
// coordinates to screen coordinates.
if (e.Button == MouseButtons.Left)
{
isDrag = true;
}
Control control = (Control)sender;
// Calculate the startPoint by using the PointToScreen
// method.
startPoint = control.PointToScreen(new Point(e.X, e.Y));
}
private Point lastPosition = Point.Empty;
private void SelectionTest_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (lastPosition == e.Location)
return;
lastPosition = e.Location;
// If the mouse is being dragged,
// undraw and redraw the rectangle as the mouse moves.
if (isDrag)
// Hide the previous rectangle by calling the
// DrawReversibleFrame method with the same parameters.
{
ControlPaint.DrawReversibleFrame(theRectangle, this.BackColor, FrameStyle.Dashed);
// Calculate the endpoint and dimensions for the new
// rectangle, again using the PointToScreen method.
Point endPoint = ((Control)sender).PointToScreen(new Point(e.X, e.Y));
int width = endPoint.X - startPoint.X;
int height = endPoint.Y - startPoint.Y;
theRectangle = new Rectangle(startPoint.X, startPoint.Y, width, height);
// Draw the new rectangle by calling DrawReversibleFrame
// again.
ControlPaint.DrawReversibleFrame(theRectangle, this.BackColor, FrameStyle.Dashed);
}
}
private void SelectionTest_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
// If the MouseUp event occurs, the user is not dragging.
isDrag = false;
// Draw the rectangle to be evaluated. Set a dashed frame style
// using the FrameStyle enumeration.
ControlPaint.DrawReversibleFrame(theRectangle,
this.BackColor, FrameStyle.Dashed);
// Find out which controls intersect the rectangle and
// change their color. The method uses the RectangleToScreen
// method to convert the Control's client coordinates
// to screen coordinates.
Rectangle controlRectangle;
for (int i = 0; i < Controls.Count; i++)
{
controlRectangle = Controls[i].RectangleToScreen
(Controls[i].ClientRectangle);
if (controlRectangle.IntersectsWith(theRectangle))
{
Controls[i].BackColor = Color.BurlyWood;
}
}
// Reset the rectangle.
theRectangle = new Rectangle(0, 0, 0, 0);
}
private void ckbTimerEnabled_CheckedChanged(object sender, EventArgs e)
{
timer.Enabled = ckbTimerEnabled.Checked;
}
#region Designer
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.ckbTimerEnabled = new System.Windows.Forms.CheckBox();
this.comboBox1 = new System.Windows.Forms.ComboBox();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// ckbTimerEnabled
//
this.ckbTimerEnabled.AutoSize = true;
this.ckbTimerEnabled.Location = new System.Drawing.Point(3, 114);
this.ckbTimerEnabled.Name = "ckbTimerEnabled";
this.ckbTimerEnabled.Size = new System.Drawing.Size(120, 17);
this.ckbTimerEnabled.TabIndex = 7;
this.ckbTimerEnabled.Text = "This.Invalidate timer";
this.ckbTimerEnabled.UseVisualStyleBackColor = true;
this.ckbTimerEnabled.CheckedChanged += new System.EventHandler(this.ckbTimerEnabled_CheckedChanged);
//
// comboBox1
//
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point(29, 28);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(121, 21);
this.comboBox1.TabIndex = 6;
//
// button1
//
this.button1.Location = new System.Drawing.Point(29, 64);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 5;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(165, 36);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 4;
this.label1.Text = "label1";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(130, 74);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(35, 13);
this.label2.TabIndex = 8;
this.label2.Text = "label2";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(130, 98);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(35, 13);
this.label3.TabIndex = 9;
this.label3.Text = "label3";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(171, 74);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(35, 13);
this.label4.TabIndex = 10;
this.label4.Text = "label4";
//
// SelectionTest
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.label4);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.ckbTimerEnabled);
this.Controls.Add(this.comboBox1);
this.Controls.Add(this.button1);
this.Controls.Add(this.label1);
this.Name = "SelectionTest";
this.Size = new System.Drawing.Size(226, 134);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox ckbTimerEnabled;
private System.Windows.Forms.ComboBox comboBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
#endregion
}
}
Form1.cs - Contains the SelectionTest and two side panels
using System;
using System.Drawing;
using System.Windows.Forms;
namespace DrawReversibleFrameTest
{
public partial class Form1 : Form
{
Timer invalidateTimer = new Timer();
public Form1()
{
InitializeComponent();
invalidateTimer.Interval = 100;
invalidateTimer.Tick += invalidateTimer_Tick;
invalidateTimer.Enabled = ckbInvalidateTimer.Checked;
}
void invalidateTimer_Tick(object sender, EventArgs e)
{
this.panel1.Invalidate();
this.panel2.Invalidate();
}
private void selectionTest1_MouseMove(object sender, MouseEventArgs e)
{
if (this.ckbInvalidatePanelsOnMove.Checked)
{
this.panel1.Invalidate();
this.panel2.Invalidate();
//Console.WriteLine("Mouse move invalidate");
}
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
if (ckbPanel1DrawRectangle.Checked)
e.Graphics.FillRectangle(Brushes.Red, new Rectangle(new Point(2, 2), new Size(20, 60)));
}
private void Panel2_Paint(object sender, PaintEventArgs e)
{
if (ckbPanel2DrawRectangle.Checked)
e.Graphics.FillRectangle(Brushes.Red, new Rectangle(new Point(2, 2), new Size(20, 60)));
}
private void ckbInvalidateTimer_CheckedChanged(object sender, EventArgs e)
{
invalidateTimer.Enabled = ckbInvalidateTimer.Checked;
}
#region Designer
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel();
this.ckbInvalidatePanelsOnMove = new System.Windows.Forms.CheckBox();
this.ckbInvalidateTimer = new System.Windows.Forms.CheckBox();
this.ckbPanel1Visible = new System.Windows.Forms.CheckBox();
this.ckbPanel2Visible = new System.Windows.Forms.CheckBox();
this.ckbPanel1DrawRectangle = new System.Windows.Forms.CheckBox();
this.ckbPanel2DrawRectangle = new System.Windows.Forms.CheckBox();
this.selectionTest1 = new DrawReversibleFrameTest.SelectionTest();
this.SuspendLayout();
//
// panel1
//
this.panel1.BackColor = System.Drawing.SystemColors.Highlight;
this.panel1.Location = new System.Drawing.Point(12, 12);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(49, 184);
this.panel1.TabIndex = 5;
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.Panel1_Paint);
//
// panel2
//
this.panel2.BackColor = System.Drawing.SystemColors.Highlight;
this.panel2.Location = new System.Drawing.Point(350, 12);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(49, 130);
this.panel2.TabIndex = 6;
this.panel2.Paint += new System.Windows.Forms.PaintEventHandler(this.Panel2_Paint);
//
// ckbInvalidatePanelsOnMove
//
this.ckbInvalidatePanelsOnMove.AutoSize = true;
this.ckbInvalidatePanelsOnMove.Location = new System.Drawing.Point(12, 256);
this.ckbInvalidatePanelsOnMove.Name = "ckbInvalidatePanelsOnMove";
this.ckbInvalidatePanelsOnMove.Size = new System.Drawing.Size(150, 17);
this.ckbInvalidatePanelsOnMove.TabIndex = 7;
this.ckbInvalidatePanelsOnMove.Text = "Invalidate panels on move";
this.ckbInvalidatePanelsOnMove.UseVisualStyleBackColor = true;
//
// ckbInvalidateTimer
//
this.ckbInvalidateTimer.AutoSize = true;
this.ckbInvalidateTimer.Location = new System.Drawing.Point(12, 279);
this.ckbInvalidateTimer.Name = "ckbInvalidateTimer";
this.ckbInvalidateTimer.Size = new System.Drawing.Size(97, 17);
this.ckbInvalidateTimer.TabIndex = 8;
this.ckbInvalidateTimer.Text = "Invalidate timer";
this.ckbInvalidateTimer.UseVisualStyleBackColor = true;
this.ckbInvalidateTimer.CheckedChanged += new System.EventHandler(this.ckbInvalidateTimer_CheckedChanged);
//
// ckbPanel1Visible
//
this.ckbPanel1Visible.AutoSize = true;
this.ckbPanel1Visible.Checked = true;
this.ckbPanel1Visible.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbPanel1Visible.Location = new System.Drawing.Point(168, 256);
this.ckbPanel1Visible.Name = "ckbPanel1Visible";
this.ckbPanel1Visible.Size = new System.Drawing.Size(94, 17);
this.ckbPanel1Visible.TabIndex = 9;
this.ckbPanel1Visible.Text = "Panel 1 visible";
this.ckbPanel1Visible.UseVisualStyleBackColor = true;
this.ckbPanel1Visible.CheckedChanged += new System.EventHandler(this.ckbPanel1Visible_CheckedChanged);
//
// ckbPanel2Visible
//
this.ckbPanel2Visible.AutoSize = true;
this.ckbPanel2Visible.Checked = true;
this.ckbPanel2Visible.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbPanel2Visible.Location = new System.Drawing.Point(168, 279);
this.ckbPanel2Visible.Name = "ckbPanel2Visible";
this.ckbPanel2Visible.Size = new System.Drawing.Size(94, 17);
this.ckbPanel2Visible.TabIndex = 10;
this.ckbPanel2Visible.Text = "Panel 2 visible";
this.ckbPanel2Visible.UseVisualStyleBackColor = true;
this.ckbPanel2Visible.CheckedChanged += new System.EventHandler(this.ckbPanel2Visible_CheckedChanged);
//
// ckbPanel1DrawRectangle
//
this.ckbPanel1DrawRectangle.AutoSize = true;
this.ckbPanel1DrawRectangle.Checked = true;
this.ckbPanel1DrawRectangle.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbPanel1DrawRectangle.Location = new System.Drawing.Point(268, 256);
this.ckbPanel1DrawRectangle.Name = "ckbPanel1DrawRectangle";
this.ckbPanel1DrawRectangle.Size = new System.Drawing.Size(135, 17);
this.ckbPanel1DrawRectangle.TabIndex = 11;
this.ckbPanel1DrawRectangle.Text = "Panel 1 draw rectangle";
this.ckbPanel1DrawRectangle.UseVisualStyleBackColor = true;
//
// ckbPanel2DrawRectangle
//
this.ckbPanel2DrawRectangle.AutoSize = true;
this.ckbPanel2DrawRectangle.Checked = true;
this.ckbPanel2DrawRectangle.CheckState = System.Windows.Forms.CheckState.Checked;
this.ckbPanel2DrawRectangle.Location = new System.Drawing.Point(268, 279);
this.ckbPanel2DrawRectangle.Name = "ckbPanel2DrawRectangle";
this.ckbPanel2DrawRectangle.Size = new System.Drawing.Size(135, 17);
this.ckbPanel2DrawRectangle.TabIndex = 12;
this.ckbPanel2DrawRectangle.Text = "Panel 2 draw rectangle";
this.ckbPanel2DrawRectangle.UseVisualStyleBackColor = true;
//
// selectionTest1
//
this.selectionTest1.BackColor = System.Drawing.SystemColors.ControlDark;
this.selectionTest1.Location = new System.Drawing.Point(67, 12);
this.selectionTest1.Name = "selectionTest1";
this.selectionTest1.Size = new System.Drawing.Size(277, 238);
this.selectionTest1.TabIndex = 0;
this.selectionTest1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.selectionTest1_MouseMove);
//
// Form7_DrawReversibleRectangleTest
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(449, 313);
this.Controls.Add(this.ckbPanel2DrawRectangle);
this.Controls.Add(this.ckbPanel1DrawRectangle);
this.Controls.Add(this.ckbPanel2Visible);
this.Controls.Add(this.ckbPanel1Visible);
this.Controls.Add(this.ckbInvalidateTimer);
this.Controls.Add(this.ckbInvalidatePanelsOnMove);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.Controls.Add(this.selectionTest1);
this.Name = "Form7_DrawReversibleRectangleTest";
this.Text = "Form7_DrawReversibleRectangleTest";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private DrawReversibleFrameTest.SelectionTest selectionTest1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.CheckBox ckbInvalidatePanelsOnMove;
private System.Windows.Forms.CheckBox ckbInvalidateTimer;
private System.Windows.Forms.CheckBox ckbPanel1Visible;
private System.Windows.Forms.CheckBox ckbPanel2Visible;
private System.Windows.Forms.CheckBox ckbPanel1DrawRectangle;
private System.Windows.Forms.CheckBox ckbPanel2DrawRectangle;
#endregion
private void ckbPanel1Visible_CheckedChanged(object sender, EventArgs e)
{
this.panel1.Visible = ckbPanel1Visible.Checked;
}
private void ckbPanel2Visible_CheckedChanged(object sender, EventArgs e)
{
this.panel2.Visible = ckbPanel2Visible.Checked;
}
}
}
Upvotes: 0
Views: 1329
Reputation: 4804
Pseudo logic for what you're trying to do is:
MouseDown:
MouseMove (w/ left button down):
MouseUp:
To Troubleshoot: Create a wrapper function for DrawReversibleFrame() calls. After drawing the rectangle, print the rectangle bounds to the debug window. Verify that each rectangle is drawn twice as you drag your mouse across the control.
Upvotes: 0