Update a drawing without deleting the previous one

I created this within a drawing method

private void draws()
{
 Bitmap bmp = new Bitmap(pictureBox17.Width, pictureBox17.Height);
 using (Graphics g = Graphics.FromImage(bmp))
            {
                //define area do pictureBox17 e preenche a branco
                Brush brush = new SolidBrush(Color.White);
                Rectangle area = new Rectangle(0, 0, pictureBox17.Width, pictureBox17.Height);
                g.FillRectangle(brush, area);
                //desenha as linhas do rectangulo
                g.DrawLine(new Pen(Color.Black), esp, esp, esp, yWcorrigidoesp);
// some more lines
}
 pictureBox17.Image = bmp;
}

And it draws exactly what i want. But imagine that after this i wanna update the exactly same drawing, ADDING a few lines, without having to draw all again, is this possible? Obviously i'm using the method

draws();

And then i wanna add something, any tips?


The way to do it is to create a DrawAction class which holds all the data needed for the things you want to draw: The Point data, the Pen or Brush etc.

Then you create and manage a List<DrawAction> drawActions and then you have a choice:

  • You either do all the drawing 'live' in the Paint event of the PictureBox or a Panel (or any control with a Paint event) by looping over the list..

  • ..Or you add the new Actions into the Bitmap Image you are building up.

What is better really depends: do you expect to do dynamic drawing, say by user actions? Do you want an undo/redo option? Then live drawing onto the control surface is a little better suited.

Or is the list of things to do fixed or derive from a fixed set of data and meant to be eventually saved to disk. That sounds more like drawing into the bitmap.

Both can also be combined, maybe collect a number of actions while keeping the option of undoing (by removing the last list item) and offering an Apply button to pump them into the bitmap..

Note: The key to drawing stuff is to keep the drawing data ready in a list so you can use it when you need it again, expand and delete the list and even change it: It would be a simple two-liner to go over all the actions and to change the Color or the Width or the LineStyle of the Pen or shifting Points a little etc etc!

When you create the DrawAction class it helps if you can decide which actions you will need. If you can't you still can go for a more extended class with enough members to work with all the many options the Graphics class offers: Drawxx, Fillxxx, Pen properties, Colors maybe even zoom..

For starters a Type, a List<Point> a float PenWidth and a Color will do..

Here is an example with a simple class, the Paint event, code to add a few test actions and both:

  • A button to do live drawing and..
  • ..one to apply the action to the apply the actions to the bitmap Image of a PictureBox.

The test data are one Line and one set of Polylines.

You should start improving on it by defining an Enum with all the types of drawing actions you want to use! This will be much better and easier to understand the the cheapo character type I have coded ;-) The types could include Rectangle, FilledRectangle, Ellipse, FilledEllipse, Line, Lines, Polygon, FilledPolygon, Text, Curve, Curves and then some. With a little extra you can also work with Image, GraphicsPath, Spline.. And other data could control Rotation, Scaling, Gradients, Transparency etc..

List<DrawAction> actions = new List<DrawAction>();

public class DrawAction
{
    public char type { get; set; }             // this should be an Enum!
    public Color color { get; set; }     
    public float penWidth { get; set; }        // only one of many Pen properties!
    public List<Point> points { get; set; }    // use PointF for more precision

    public DrawAction(char type_, Color color_, float penwidth_)
    {
        type = type_; color = color_; penWidth = penwidth_;
        points = new List<Point>();
    }
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Draw(e.Graphics, actions);
}


public void Draw(Graphics G, List<DrawAction> actions)
{
    foreach (DrawAction da in actions)
        if (da.type == 'L' && da.points.Count > 1)
            using (Pen pen = new Pen(da.color, da.penWidth))
                G.DrawLine(pen, da.points[0], da.points[1]);
        else if (da.type == 'P' && da.points.Count > 1)
            using (Pen pen = new Pen(da.color, da.penWidth))
                G.DrawLines(pen, da.points.ToArray());
    // else..


}

private void button1_Click(object sender, EventArgs e)
{
    AddTestActions();
    pictureBox1.Invalidate();
}

private void button2_Click(object sender, EventArgs e)
{
    AddTestActions();
    Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, 
                            pictureBox1.ClientSize.Height);
    using (Graphics G = Graphics.FromImage(bmp))  Draw(G, actions);
    pictureBox1.Image = bmp;
}

void AddTestActions()
{
    actions.Add(new DrawAction('L', Color.Blue, 3.3f));
    actions[0].points.Add(new Point(23, 34));
    actions[0].points.Add(new Point(23, 134));
    actions.Add(new DrawAction('P', Color.Red, 1.3f));
    actions[1].points.Add(new Point(11, 11));
    actions[1].points.Add(new Point(55, 11));
    actions[1].points.Add(new Point(55, 77));
    actions[1].points.Add(new Point(11, 77));
}

The result for both looks the same:

enter image description here

Update Another, maybe more appropriate design would be to have a Draw (Graphics g) method in the DrawAction class. Then in a Paint event this would do: foreach (DrawAction da in drawActionList) da.Draw(e.Graphics);


Try creating the Bitmap outside of the function to preserve it, the way you are doing it now disposes of the Bitmap element after the function has completed.

you could then do something like

Bitmap bmp = new Bitmap(pictureBox17.Width, pictureBox17.Height);
    private void draws()
    {
     if (bmp ==null)
     using (Graphics g = Graphics.FromImage(bmp))
                {
                    //define area do pictureBox17 e preenche a branco
                    Brush brush = new SolidBrush(Color.White);
                    Rectangle area = new Rectangle(0, 0, pictureBox17.Width, pictureBox17.Height);
                    g.FillRectangle(brush, area);
                    //desenha as linhas do rectangulo
                    g.DrawLine(new Pen(Color.Black), esp, esp, esp, yWcorrigidoesp);
                 }
   else {
         using (Graphics g = Graphics.FromImage(bmp))
         {
            g.DrawLine(new Pen(Color.Black), esp, esp, esp, yWcorrigidoesp);
         }
    // some more lines
    }
     pictureBox17.Image = bmp;
    }

Just to get you started.. :)

Alternatively you can pass the bmp into the draw function if you use this method to draw multiple bmp at different times