0

I need some help with my code as I'm doing something wrong! I tried to fix and do everything in the proper way but I was make more problems... The code below is the prototype of a Pain-like program which I want to create after I know everything what I need and the prototype works perfectly.

I share the full code here and I also give a link to download my full project/solution, so you can run it to see what's happening or modify to test.

DOWNLOAD: PainPrototype_BushWookie

The main issues:

  • I want to create rectangles by mouse button in the way how this function used to work in the drawing apps. This means when my mouse down I start to draw rect, when I move the mouse in the canvas the rect dynamically change its size to adapt. When my mouse up, the rect is done. I'm sure you know what I'm talking about as this is the way how almost all of the paint software draw shapes including rectangles. I could to code this, but I'm not sure I did on the proper way, and I also has an issue with this! After I draw a rect, and I click again to draw another one, the previous rect is vanished/deleted.. I need to fix this with your help!

Worth to note when I use my "graphics" object (commented in code) and not "e.Graphics" to draw the rect this will draw the shape every thick/frame/update.. so when I move the mouse I will be hundreds of rect as this draw continuously..

  • I also have "brush" tool which can draw on the canvas like the brush tool in Photoshop or pen tool in the basic Windows Paint. This part is working fine! But I draw from the 'MouseMove' event and not from the Paint. I saw this in a tutorial video but everybody say "draw only from Pain event".. When I tried to do this, the functionality of the brush has ruined. This means, it draw straight line and not "chase" the mouse cursor and draw after its path. Can you help me clarify this, and how this should work from the Paint event?

  • My last issue is emerged when I clicked my "Clear" button, which should clear down the whole canvas! This worked fine with my Brush (only when I draw from MouseMove) and Pixel tool (draw a single pixel) and cleared the canvas properly.. The rectangle what I draw from Paint event is not cleared. Also the rect stay there after I push "new" button to get new empty canvas. This is big problem! If I clear it from Paint event it will do, but this cause the Paint event call in an infinite loop..

This are my main issues and I need help with them! Also I have some question about the drawing/graphics what I can't understand properly from the MSDN:

  • Why should I draw only from Pain event? or turn outside the question, what's wrong if I draw from MouseMove?
  • What is the OnPaint() and why is it different from Paint()?
  • Invalidate().. I know this will call the Paint event, but anything else to know? Why is it different from any other method which cause the Paint event to redraw?

I also would like to ask to overview every line of my code to help learn what I can improve as I'm quite new in C#! For instance, when I try to release the old picturebox from the "New" button.. all of my lines are necessary? or did I miss something? etc etc.. or my code logic is good, or should I do in other way?

Thanks in advance for your time! Would be the best if you download my full solution from the link above to see through easier and run/test it! I just want to give help to fix my issues and learn coding the proper way! Thanks again, I appreciate any help!


Here my code, please notice the difference between pbx_canvas and bmp_canvas! (I want to draw on a bitmap as I want to save the created images later).

namespace NewFile___PixelDraw___Pen___Shapes
{
    public partial class Form1 : Form
    {
        PictureBox pbx_usedColorBox;
        PictureBox pbx_canvas;
        Bitmap btm_canvas;
        Graphics graphics;
        Pen pen;

        Button btn_activeTool = new Button();
        Point initialMouse = new Point(-1, -1);
        Color chosenColor = Color.White;
        bool mouseDown;

        Rectangle rect;

        public Form1()
        {
            InitializeComponent();

            pen = new Pen(chosenColor, (float)num_drawWidth.Value)
            {
                StartCap = System.Drawing.Drawing2D.LineCap.Round,
                EndCap = System.Drawing.Drawing2D.LineCap.Round,
            };

            tbx_canvasX.MaxLength = 5;
            tbx_canvasY.MaxLength = 5;

            pbx_usedColorBox = pbx_cBlack;
            pnl_cPalette.BackColor = chosenColor;
            pbx_cWhite.BorderStyle = BorderStyle.None;
        }

        // NEW
        private void btn_new_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(tbx_canvasX.Text) && !string.IsNullOrEmpty(tbx_canvasY.Text))
            {
                Point offset = new Point(7, 19);
                Point canvasSize = new Point(int.Parse(tbx_canvasX.Text), int.Parse(tbx_canvasY.Text));
                Point canvasLocation = new Point((Size.Width / 2) - (canvasSize.X / 2) - offset.X, (Size.Height / 2) - (canvasSize.Y / 2) - offset.Y);

                if (canvasSize.X != 0 && canvasSize.Y != 0)
                {
                    if (pbx_canvas != null && btm_canvas != null)
                    {
                        pbx_canvas.MouseDown -= Pbx_canvas_MouseDown;
                        pbx_canvas.MouseMove -= Pbx_canvas_MouseMove;
                        pbx_canvas.MouseUp -= Pbx_canvas_MouseUp;
                        Controls.Remove(pbx_canvas);
                        pbx_canvas.Dispose();
                        btm_canvas.Dispose();
                        graphics.Dispose();
                    }

                    pbx_canvas = new PictureBox
                    {
                        Size = new Size(canvasSize.X, canvasSize.Y),
                        BorderStyle = BorderStyle.FixedSingle,
                        Anchor = AnchorStyles.None,
                        Location = canvasLocation,
                        BackColor = Color.White,
                        Cursor = Cursors.Cross,
                    };

                    Controls.Add(pbx_canvas);
                    pbx_canvas.MouseDown += Pbx_canvas_MouseDown;
                    pbx_canvas.MouseMove += Pbx_canvas_MouseMove;
                    pbx_canvas.MouseUp += Pbx_canvas_MouseUp;
                    pbx_canvas.Paint += Pbx_canvas_Paint;

                    btm_canvas = new Bitmap(canvasSize.X, canvasSize.Y);
                    graphics = Graphics.FromImage(btm_canvas);

                    if (cbx_smoothing.Checked)
                    {
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                    }
                }
                else
                {
                    tbx_canvasX.Text = "";
                    tbx_canvasY.Text = "";
                }
            }
        }

        // PAINT
        private void Pbx_canvas_Paint(object sender, PaintEventArgs e)
        {
            Debug.WriteLine("IN PAINT EVENT!");

            e.Graphics.DrawRectangle(pen, rect);
            //graphics.DrawRectangle(pen, rect);
        }

        // M > DOWN
        private void Pbx_canvas_MouseDown(object sender, MouseEventArgs e)
        {
            switch (btn_activeTool.Name)
            {
                case "btn_toolBrush":
                    mouseDown = true;
                    initialMouse = e.Location;
                    break;
                case "btn_toolRect":
                    mouseDown = true;
                    initialMouse = e.Location;
                    rect = new Rectangle(initialMouse, new Size(0, 0));
                    break;
            }
        }

        // M > MOVE
        private void Pbx_canvas_MouseMove(object sender, MouseEventArgs e)
        {
            switch (btn_activeTool.Name)
            {
                case "btn_toolBrush":
                    if (mouseDown == true && initialMouse.X != -1 && initialMouse.Y != -1)
                    {
                        graphics.DrawLine(pen, initialMouse, e.Location);
                        pbx_canvas.Image = btm_canvas;
                        initialMouse = e.Location;
                    }
                    break;
                case "btn_toolRect":
                    if (mouseDown == true && initialMouse.X != -1 && initialMouse.Y != -1)
                    {
                        rect = new Rectangle(Math.Min(e.X, initialMouse.X),
                                             Math.Min(e.Y, initialMouse.Y),
                                             Math.Abs(e.X - initialMouse.X),
                                             Math.Abs(e.Y - initialMouse.Y));

                        pbx_canvas.Image = btm_canvas;
                        //Invalidate(rect);
                    }
                    break;
            }
        }

        // M > UP
        private void Pbx_canvas_MouseUp(object sender, MouseEventArgs e)
        {
            switch (btn_activeTool.Name)
            {
                case "btn_toolPixel":
                    btm_canvas.SetPixel(e.X, e.Y, chosenColor);
                    pbx_canvas.Image = btm_canvas; 
                    break;
                case "btn_toolBrush": 
                    mouseDown = false;
                    initialMouse.X = -1;
                    initialMouse.Y = -1;
                    break;
                case "btn_toolRect":
                    mouseDown = false;
                    initialMouse.X = -1;
                    initialMouse.Y = -1;
                    break;
            }
        }

        // PIXEL
        private void btn_toolPixel_Click(object sender, EventArgs e)
        {
            btn_activeTool.BackColor = Color.GhostWhite;
            btn_activeTool = (Button)sender;
            btn_activeTool.BackColor = Color.Beige;
        }

        // BRUSH
        private void btn_toolBrush_Click(object sender, EventArgs e)
        {
            btn_activeTool.BackColor = Color.GhostWhite;
            btn_activeTool = (Button)sender;
            btn_activeTool.BackColor = Color.Beige;
        }

        // RECT
        private void btn_toolRect_Click(object sender, EventArgs e)
        {
            btn_activeTool.BackColor = Color.GhostWhite;
            btn_activeTool = (Button)sender;
            btn_activeTool.BackColor = Color.Beige;
        }

        // CLEAR
        private void btn_clear_Click(object sender, EventArgs e)
        {
            graphics.Clear(pbx_canvas.BackColor);
            pbx_canvas.Image = null;
        }

        // Color change
        private void G_pbx_colors_Click(object sender, EventArgs e)
        {
            PictureBox pbx_sender = (PictureBox)sender;

            pbx_usedColorBox.BorderStyle = BorderStyle.FixedSingle;
            pbx_sender.BorderStyle = BorderStyle.None;
            pbx_usedColorBox = pbx_sender;

            chosenColor = pbx_sender.BackColor;
            pnl_cPalette.BackColor = chosenColor;
            pen.Color = chosenColor;
        }

        // CanvasSize
        private void G_tbx_canvasSize_Enter(object sender, EventArgs e)
        {
            BeginInvoke(new Action(() => (sender as TextBox).SelectAll()));
        }
        private void G_tbx_canvasSize_KeyPress(object sender, KeyPressEventArgs e)
        {
            e.Handled = !char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar);
        }

        // DrawWidth
        private void num_drawWidth_ValueChanged(object sender, EventArgs e)
        {
            pen.Width = (float)num_drawWidth.Value;
        }
    }
}
BushWookie
  • 51
  • 1
  • 1
  • 8
  • Instead of posting the full coee and writing so much of yur application you shoud have started by learning about the basics first. You will have to follow the rules and probably change much of your code. - Step one understand the difference between persistent drawing and non-persistent drawing. If your Rectangles are supposed to be rubberband rectangles use code like [this](https://stackoverflow.com/questions/33568377/deleting-drawn-rectangle-zoom-box-after-zooming-in/33569687#33569687) - For the 'real' thing you nedd to decide if they are dynamic and draw them along with everythins else.... – TaW Jan 16 '21 at 17:01
  • ..each time anything is changed or the system needs to derae. __Or__ if you want to draw into a Bitmap. For the difference [see here](https://stackoverflow.com/questions/27337825/picturebox-paintevent-with-other-method/27341797?r=SearchResults&s=2|21.0833#27341797) – TaW Jan 16 '21 at 17:02
  • 1
    Simple example: [How to use the Paint event to draw shapes at mouse coordinates](https://stackoverflow.com/a/53708936/7444103). Use only the Paint event, or override `OnPaint()` in a Custom Control / User Control / Form: `OnPaint()` is the method that raises the Paint event. Call `Invalidate()` when you need to repaint your canvas. + @TaW didn't post it, so I'll do it: [Creating different brush patterns](https://stackoverflow.com/a/49298313/7444103). These should get you started. – Jimi Jan 16 '21 at 17:04
  • #TaW - You can tell the same thing to 90% of the people who are asking question here... Which is a simple task for you, a beginner can confuse easily on it and sometimes also hard to look after properly on the net for a beginner. That's why I thought better to share everything to check it... Anyway, thanks for both of you for the links very helpful! Still confusing where should I write my drawing stuffs for dynamic shapes as one people say different like the others. I try to fix my code! Thanks! – BushWookie Jan 17 '21 at 19:06

1 Answers1

0

Why should I draw only from Paint event? or turn outside the question, what's wrong if I draw from MouseMove?

What do you intend to draw to? You do not have access to the graphics object for the control. You could draw to a bitmap or some other buffer if you want. But drawing in the event handlers is just not a good idea. You might have multiple input events after each other, if every event causes costly drawing, instead of a cheap invalidate, the application will not work well.

What is the OnPaint() and why is it different from Paint()?

Overriding OnPaint or using the Paint-event usually does not matter much.

Invalidate().. I know this will call the Paint event, but anything else to know? Why is it different from any other method which cause the Paint event to redraw?

This will not call the paint event. It will mark the control as needing a redraw at some later time.

There are many issues with the provided code, like

  • Using strings in switch statements
  • All logic seem to be in one class
  • You are attaching events in a button click handler
  • You are not disposing anything as far as I can see

This is a high level overview on how I would approach the problem.

When activating a tool there should be a state-machine that keeps track of the tool used, and how to paint the shape for the tool. You seem to have a primitive state machine, but I would recommend using classes to model states, so that each tool is independent of each other.

Once you are done with a tool you should then draw the completed shape to a bitmap that represents the completed graphics. This bitmap should be drawn to the screen before the graphics for the active tool. That way when you are drawing the next rectangle the previous one is stored in the bitmap.

An alternative would be to keep a buffer of shapes that you append to when you use a tool. That way you would paint each shape in turn, and have the possibility of removing, or changing it after it has been created.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thanks for the answer, I learned a lot, but for me things like "state machine using classes" is way to hard to figure out how should it be look in practice. I need to ask for more details about "Once you are done with a tool you..." part. I tried to put my 'pbx_canvas.Image = btm_canvas; ' part before the 'graphics.DrawRectangle' line but not good.. I also got a result where the rectangle drew continuously while I moved the mouse... – BushWookie Jan 17 '21 at 19:12