Andrew Truckle
Andrew Truckle

Reputation: 19197

Preventing flicker for my rubber band?

I have implemented a rubber band by adopting the following code:

https://support.microsoft.com/en-gb/kb/314945

This is my code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;

namespace Test
{
    public partial class TestX: UserControl
    {
        private Boolean m_bLeftButton { get; set; }
        private Boolean m_bMiddleButton { get; set; }
        private Boolean m_bZoomWindow { get; set; }

        Point m_ptOriginal = new Point();
        Point m_ptLast = new Point();

        public TestX()
        {
            m_bZoomWindow = false;
            m_bLeftButton = false;
            m_bMiddleButton = false;
        }

        // Called when the left mouse button is pressed. 
        public void MyMouseDown(Object sender, MouseEventArgs e)
        {
            if(m_bZoomWindow && e.Button == MouseButtons.Left)
            {
                // Make a note that we "have the mouse".
                m_bLeftButton = true;

                // Store the "starting point" for this rubber-band rectangle.
                m_ptOriginal.X = e.X;
                m_ptOriginal.Y = e.Y;
                // Special value lets us know that no previous
                // rectangle needs to be erased.
                m_ptLast.X = -1;
                m_ptLast.Y = -1;
            }
        }

        // Convert and normalize the points and draw the reversible frame.
        private void MyDrawReversibleRectangle(Point p1, Point p2)
        {
            Rectangle rc = new Rectangle();

            // Convert the points to screen coordinates.
            p1 = PointToScreen(p1);
            p2 = PointToScreen(p2);
            // Normalize the rectangle.
            if (p1.X < p2.X)
            {
                rc.X = p1.X;
                rc.Width = p2.X - p1.X;
            }
            else
            {
                rc.X = p2.X;
                rc.Width = p1.X - p2.X;
            }
            if (p1.Y < p2.Y)
            {
                rc.Y = p1.Y;
                rc.Height = p2.Y - p1.Y;
            }
            else
            {
                rc.Y = p2.Y;
                rc.Height = p1.Y - p2.Y;
            }
            // Draw the reversible frame.
            ControlPaint.DrawReversibleFrame(rc,
                            Color.WhiteSmoke, FrameStyle.Thick);
        }

        // Called when the left mouse button is released.
        public void MyMouseUp(Object sender, MouseEventArgs e)
        {
            if(m_bZoomWindow && e.Button == MouseButtons.Left)
            {
                // Set internal flag to know we no longer "have the mouse".
                m_bZoomWindow = false;
                m_bLeftButton = false;

                // If we have drawn previously, draw again in that spot
                // to remove the lines.
                if (m_ptLast.X != -1)
                {
                    Point ptCurrent = new Point(e.X, e.Y);
                    MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);

                    // Do zoom now ...
                }
                // Set flags to know that there is no "previous" line to reverse.
                m_ptLast.X = -1;
                m_ptLast.Y = -1;
                m_ptOriginal.X = -1;
                m_ptOriginal.Y = -1;
            }
        }

        // Called when the mouse is moved.
        public void MyMouseMove(Object sender, MouseEventArgs e)
        {
            Point ptCurrent = new Point(e.X, e.Y);

            if(m_bLeftButton)
            {
                // If we "have the mouse", then we draw our lines.
                if (m_bZoomWindow)
                {
                    // If we have drawn previously, draw again in
                    // that spot to remove the lines.
                    if (m_ptLast.X != -1)
                    {
                        MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);
                    }
                    // Update last point.
                    if(ptCurrent != m_ptLast)
                    {
                        m_ptLast = ptCurrent;
                        // Draw new lines.
                        MyDrawReversibleRectangle(m_ptOriginal, ptCurrent);
                    }
                }
            }
        }

        // Set up delegates for mouse events.
        protected override void OnLoad(System.EventArgs e)
        {
            MouseDown += new MouseEventHandler(MyMouseDown);
            MouseUp += new MouseEventHandler(MyMouseUp);
            MouseMove += new MouseEventHandler(MyMouseMove);
            MouseWheel += new MouseEventHandler(MyMouseWheel);

            m_bZoomWindow = false;
        }
    }
}

It itself it works, but the rectangle flashes. Other programs, like CAD packages, have zero flickering when drawing a rectangle.

My form is set to use DoubleBuffering so I thought it would be OK. Has anyone else encountered this issue?

Update: I thought I would go back to the beginning and do a test winforms project with a table layout panel and a embedded user control. I set the user control to work like the answer I was provided. The only difference was that I set the user control constructor like this:

public MyUserControl()
{
    InitializeComponent();

    _selectionPen = new Pen(Color.Black, 3.0f);

    SetStyle(ControlStyles.OptimizedDoubleBuffer | 
             ControlStyles.AllPaintingInWmPaint | 
             ControlStyles.UserPaint, true);
    BackColor = Color.Transparent;
    Dock = DockStyle.Fill;
    Margin = new Padding(1);
}

Notice the additional control styles AllPaintingInWmPaint and UserPaint? It seems that I needed these in addition to the OptimizedDoubleBuffer style. I also set the form as double buffered.

When I make those adjustments I can draw a flicker free rubber band. Hoorah! But when I add back in the embedded class for rendering a DWG in the user control, I get the conflict of one over imposing the other.

So that is where I am at and I will wait to see what I can glean from the vendors of the DWG viewer class.

Upvotes: 0

Views: 550

Answers (1)

jimbobmcgee
jimbobmcgee

Reputation: 1721

To demonstrate using the Paint event, from my comment to the OP.

Smoothest box draw I was able to get was by doing it in Paint and setting ControlStyles.OptimizeDoubleBuffer on the control. Of course, it depends on the intended bounds of your box -- this will not exceed the bounds of the control itself (i.e. will not draw onto the form or desktop):

using System.Drawing;
using System.Windows.Forms;

namespace WinformsScratch.RubberBand
{
    public class TestY : Control
    {
        private Point? _selectionStart;
        private Point? _selectionEnd;

        private readonly Pen _selectionPen;

        public TestY()
        {
            _selectionPen = new Pen(Color.Black, 3.0f);

            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            MouseDown += (s, e) => {
                if (e.Button == MouseButtons.Left) 
                    _selectionStart = _selectionEnd = e.Location;
            };

            MouseUp += (s, e) => {
                if (e.Button == MouseButtons.Left) 
                {
                    _selectionStart = _selectionEnd = null; 
                    Invalidate(false); 
                }
            };

            MouseMove += (s, e) => {
                if (_selectionStart.HasValue &&
                    _selectionEnd.HasValue &&
                    _selectionEnd.Value != e.Location)
                {
                    _selectionEnd = e.Location;
                    Invalidate(false);
                }
            };

            Paint += (s, e) => {
                if (_selectionStart.HasValue && _selectionEnd.HasValue)
                    e.Graphics.DrawRectangle(_selectionPen, GetSelectionRectangle());
            };
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_selectionPen != null) _selectionPen.Dispose();
            }
            base.Dispose(disposing);
        }

        private Rectangle GetSelectionRectangle()
        {
            Rectangle rc = new Rectangle();

            if (_selectionStart.HasValue && _selectionEnd.HasValue)
            {
                // Normalize the rectangle.
                if (_selectionStart.Value.X < _selectionEnd.Value.X)
                {
                    rc.X = _selectionStart.Value.X;
                    rc.Width = _selectionEnd.Value.X - _selectionStart.Value.X;
                }
                else
                {
                    rc.X = _selectionEnd.Value.X;
                    rc.Width = _selectionStart.Value.X - _selectionEnd.Value.X;
                }

                if (_selectionStart.Value.Y < _selectionEnd.Value.Y)
                {
                    rc.Y = _selectionStart.Value.Y;
                    rc.Height = _selectionEnd.Value.Y - _selectionStart.Value.Y;
                }
                else
                {
                    rc.Y = _selectionEnd.Value.Y;
                    rc.Height = _selectionStart.Value.Y - _selectionEnd.Value.Y;
                }
            }

            return rc;
        }
    }
}

Upvotes: 1

Related Questions