【问题标题】:DrawReversibleFrame unwanted behavior (disappear, messy draw) when side controls Invalidates侧面控件无效时 DrawReversibleFrame 不需要的行为(消失、乱画)
【发布时间】:2013-12-27 19:48:06
【问题描述】:

我正在使用 DrawReversibleFrame 根据鼠标在名为 SelectionTest 的控件上的位置绘制一个反色矩形。使用 DrawReversibleFrame 的基本代码取自 MSDN sample

如果在区域选择期间重绘控件(在无效或刷新之后),矩形可能会消失或具有不同大小的多个副本。你可能会问我,你为什么要刷新控件?其实我不是!控件 Paint 事件也不会引发。

我发现只有当矩形被绘制在其他两个很快失效的控件之间时才会发生重绘(例如:鼠标移动事件或计时器)。

为了重现这个主题,我创建了两个示例类:

SelectionTest,处理 DrawReversibleFrame。复选框 This.Invalidate timer,启用/禁用调用 SelectionTest 的 Invalidate 的计时器。如果您激活它,您可以轻松重现不需要的 DrawReversibleFrame 行为。请注意,这(无效调用)只是重现错误的“作弊”。在我的应用程序上,我没有调用 Invalidate 或要求以任何方式重绘控件。正如我所说,在我的应用程序上没有引发 Paint 事件。

Form1,拥有两个面板之间的 SelectionTest。它拥有两个主要的复选框,分别控制鼠标移动时的无效和无效计时器。如果两者都被选中,不需要的行为会更加明显。

这是不受欢迎行为的屏幕截图。注意中心(较暗区域)控件右上角的选择:

问题:

  1. DrawReversibleFrame 矩形怎么可能像在 Paint 事件中一样消失,但没有引发该事件?
  2. 如何解决侧边控件导致的屏幕重绘问题?
  3. 有没有办法使用图形绘制一个反色矩形? 示例代码:

SelectionTest.cs - 使用基于 MSDN sample 的 DrawReversibleFrame 的控件。

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 - 包含 SelectionTest 和两个侧面板

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

【问题讨论】:

  • 您需要在周围保留一个 bool 以指示需要擦除矩形。在绘制方法中将其设置为 false
  • @HansPassant,对不起,我没明白你的意思。通过调用具有相同参数的方法 DrawReversibleFrame 来擦除矩形。问题是,有时当我尝试擦除它时,它实际上已被绘制,因为它已经被由侧面板引起的某些 Windows 重绘(不是我调用的)擦除,如上所述。
  • @HansPassant,我已经尝试过了,问题是没有引发 Paint 事件。如何重绘图像但未调用“受保护的覆盖无效 OnPaint(PaintEventArgs e)”??

标签: c# winforms drawing


【解决方案1】:

你想要做的伪逻辑是:

鼠标按下:

  1. 绘制矩形。
  2. 保存矩形边界。

鼠标移动(左键向下):

  1. 重绘前一个矩形以擦除。
  2. 计算下一个矩形。
  3. 绘制下一个矩形。
  4. 保存矩形边界。
  5. 冲洗/重复(无限期)

鼠标上移:

  1. 重绘最后一个要擦除的矩形。

疑难解答:为 DrawReversibleFrame() 调用创建一个包装函数。绘制矩形后,将矩形边界打印到调试窗口。当您在控件上拖动鼠标时,验证每个矩形是否被绘制了两次。

【讨论】:

  • 这正是我正在做的。但结果并非我们通常预期的那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 2010-10-07
相关资源
最近更新 更多