Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep drawn shapes after the form is refreshed?

Tags:

c#

winforms

gdi+

I am trying to create a small paint application in Visual Studio 2015. My project falls into the category of Windows Form Applications. I have the following problem:

 private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (a == 1)
        {
            if (r == 1 || el == 1)
            {
                int x = Math.Min(inX, e.X);
                int y = Math.Min(inY, e.Y);
                int width = Math.Max(inX, e.X) - Math.Min(inX, e.X);
                int height = Math.Max(inY, e.Y) - Math.Min(inY, e.Y);
                rect = new Rectangle(x, y, width, height);
                Refresh();
            }
            else if (l == 1)
            {
                ep = e.Location;
                Refresh();
            }
            else
            {
                ep = e.Location;
                g = this.CreateGraphics();
                g.DrawLine(p, sp, ep);
                sp = ep;
            }
        }
    }

This part of my codes creates a Rectangular (2nd if), a line segment(3rd if) and just a line. It works pretty much the same as MS Paint; the rectangular or the line segment isn not completed until the user releases the left mouse click (Mouse up). But when a rectangular is finally made when I try again to create another one, the form refreshes ( Refresh(); ) and I lose all the previous drawn rectangulars or lines. I tried replacing Refresh(); with Invalidate(rect); and Update();, but I do not get the result I want.

Instead, I get this:

enter image description here

like image 959
Xaris Avatar asked Jan 06 '16 17:01

Xaris


Video Answer


2 Answers

You should do all your drawing to a separate Bitmap "buffer" that you keep around. Draw your shapes to that bitmap, then when the screen actually needs to be updated, draw the buffer to the screen.

Also, anytime you call Graphics.FromImage you need to remember to Dispose, or it will leak resources like crazy.

Incredibly simple example

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

namespace DrawExample
{
    public partial class Form1 : Form
    {

        private Bitmap _canvas; //This is the offscreen drawing buffer
        private Point _anchor; //The start point for click-drag operations
        private Rectangle? _ghost;
        private Brush _ghostBrush;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            _ghostBrush = new SolidBrush(Color.FromArgb(200, 200, 200, 255)); //This creates a slightly blue, transparent brush for the ghost preview
            ResizeCanvas();
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            ResizeCanvas();
        }

        /// <summary>
        /// Resizes the offscreen bitmap to match the current size of the window, it preserves what is currently in the bitmap.
        /// </summary>
        private void ResizeCanvas()
        {
            Bitmap tmp = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppRgb);
            using (Graphics g = Graphics.FromImage(tmp))
            {
                g.Clear(Color.White);
                if (_canvas != null)
                {
                    g.DrawImage(_canvas, 0, 0);
                    _canvas.Dispose();
                }
            }
            _canvas = tmp;
        }

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _anchor = new Point(e.X, e.Y);
            }
        }
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _ghost = new Rectangle(_anchor.X, _anchor.Y, e.X - _anchor.X, e.Y - _anchor.Y);
                this.Invalidate();
            }
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                //Create a Graphics for the offscreen bitmap
                using (Graphics g = Graphics.FromImage(_canvas))
                {
                    Rectangle rect = new Rectangle(_anchor.X, _anchor.Y, e.X - _anchor.X, e.Y - _anchor.Y);
                    g.FillRectangle(Brushes.White, rect);
                    g.DrawRectangle(Pens.Black, rect);
                }

                _ghost = null;

                //This queues up a redraw call for the form
                this.Invalidate();
            }
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {

            if (_ghost.HasValue)
            {
                using (Bitmap tmp = new Bitmap(_canvas))
                {
                    using (Graphics g = Graphics.FromImage(tmp))
                    {
                        g.FillRectangle(_ghostBrush, _ghost.Value);
                        g.DrawRectangle(Pens.Black, _ghost.Value);

                        e.Graphics.DrawImage(tmp, 0, 0);
                    }
                }
            }
            else
            {
                e.Graphics.DrawImage(_canvas, 0, 0);
            }
        }


        //This stops the flickering
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //Do nothing
        }
    }
}
like image 52
Bradley Uffner Avatar answered Sep 29 '22 03:09

Bradley Uffner


You are drawing directly onto the form's drawing surface. That surface is not persistent. It lasts until the next paint cycle.

Instead you should:

  1. Draw to an offscreen bitmap.
  2. Draw that bitmap onto, for instance, a picture box control. Or paint it directly onto the form's drawing surface in its Paint event.
like image 39
David Heffernan Avatar answered Sep 29 '22 03:09

David Heffernan