2

I include Application.DoEvents() in 3 different places. Don´t know why this simple app is freezing. Any idea?

It runs very well in my VM running Visual Studio 2010 in Windows 7. I move it to a Visual Studio 2019 in my host machine running Windows 10. It runs very well, but as soon I click inside it - to close the windows - it hangs the Windows control. Mouse is blocked in Visual Studio region, and I have to open Task Manager and kill the process to close the app.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Kaledoscópio
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();

            tmrStep.Tick += tmrStep_Tick;
            tmrStep.Start();
            Application.DoEvents();
        }

        private void tmrStep_Tick(object sender, EventArgs e)
        {
            Graphics g = frmMain.ActiveForm.CreateGraphics();

            Random randomColor = new Random();
            Color defaultColor = Color.FromArgb(randomColor.Next(255), randomColor.Next(255), randomColor.Next(255));
            Pen defaultPen = new Pen(defaultColor, 2);

            int intHeight = (int)g.VisibleClipBounds.Height;
            int intWidth = (int)g.VisibleClipBounds.Width;

            int intMiddleHeight = intHeight / 2;
            int intMiddleWidth = intWidth / 2;

            Point posTopLeft = new Point(0, 0);
            Point posTopRight = new Point(intWidth, 0);
            Point posBottomLeft = new Point(0, intHeight);
            Point posBottomRight = new Point(intWidth, intHeight);
            Point posMiddle = new Point(intMiddleWidth, intMiddleHeight);
            Point posMiddleTop = new Point(intMiddleWidth, 0);
            Point posMiddleLeft = new Point(0, intMiddleHeight);
            Point posMiddleRight = new Point(intWidth, intMiddleHeight);
            Point posMiddleBottom = new Point(intMiddleWidth, intHeight);

            int defaultStep = randomColor.Next(3, 10);

            int b1;
            for(b1 = 0; b1<=intMiddleWidth; b1+=defaultStep)
            {
                g.DrawLine(defaultPen, posTopLeft.X + b1, posTopLeft.Y, posMiddle.X - b1, posMiddle.Y);
                g.DrawLine(defaultPen, posTopRight.X - b1, posTopRight.Y, posMiddle.X + b1, posMiddle.Y);
                g.DrawLine(defaultPen, posMiddle.X - b1, posMiddle.Y, posBottomLeft.X + b1, posBottomLeft.Y);
                g.DrawLine(defaultPen, posMiddle.X + b1, posMiddle.Y, posBottomRight.X - b1, posBottomRight.Y);
                Application.DoEvents();
            }

            int b2;
            for( b2=0; b2<=intMiddleHeight; b2+=defaultStep)
            {
                g.DrawLine(defaultPen, posMiddleTop.X, posMiddleTop.Y + b2, posMiddleLeft.X, posMiddleLeft.Y - b2);
                g.DrawLine(defaultPen, posMiddleTop.X, posMiddleTop.Y + b2, posMiddleRight.X, posMiddleRight.Y - b2);
                g.DrawLine(defaultPen, posMiddleRight.X, posMiddleRight.Y + b2, posMiddleBottom.X, posMiddleBottom.Y - b2);
                g.DrawLine(defaultPen, posMiddleLeft.X, posMiddleLeft.Y + b2, posMiddleBottom.X, posMiddleBottom.Y - b2);
                Application.DoEvents();
            }

            Application.DoEvents();
        }
    }
}
J...
  • 30,968
  • 6
  • 66
  • 143
  • 5
    Don't draw outside of a `Paint` handler. `DoEvents()` is also pointless as the last statement in an event handler. It's also a stinky bad smell, generally, so I'd avoid using it and I'd definitely avoid using it as a core element of your design. – J... Sep 30 '20 at 18:04
  • See also : [What do DoEvents()'s in C# actually do?](https://stackoverflow.com/q/27153675/327083) – J... Sep 30 '20 at 18:06
  • And : [Use of Application.DoEvents()](https://stackoverflow.com/q/5181777/327083) – J... Sep 30 '20 at 18:07
  • [Can I use Graphics object in any other event than Paint event?](https://stackoverflow.com/q/33759262/327083) – J... Sep 30 '20 at 18:09
  • [C#.NET .. is it a bad practice to draw outside the Paint event handler?](https://stackoverflow.com/q/30503230/327083) – J... Sep 30 '20 at 18:09
  • And https://blog.codinghorror.com/is-doevents-evil/ – asawyer Sep 30 '20 at 18:12
  • What you probably want to do is to use a `PictureBox` add that to your form where you want to draw and handle the `Paint` event. Do your drawing in there, and be sure to use the `Graphics` object supplied in the handler arguments (ie: don't create a new one). – J... Sep 30 '20 at 18:18
  • And if you need an example : [How do I draw a circle and line in the picturebox?](https://stackoverflow.com/a/2729984/327083) – J... Sep 30 '20 at 18:20
  • [Here's another one](https://stackoverflow.com/q/11352301/3043) – Joel Coehoorn Sep 30 '20 at 19:43
  • 1
    Never ever call `Application.DoEvents()`. It is evil incarnate. – Enigmativity Nov 01 '20 at 08:57

1 Answers1

4

For what it's worth, I was able to reproduce the hanging behavior using the code you posted. The program did eventually respond to closing the window, but it took a very long time. I did not spend any time trying to figure out exactly what caused the program to hang, because the code you have now is so horribly incorrect, so far from the right way to use the Windows Forms API, that I didn't see any point.

Problems in the code you posted include:

  • Calling CreateGraphics(). A correctly written Winforms program should never need this.
  • Calling DoEvents(). Ditto.
  • Creating a new Random object every time you want to pick a new random number. See e.g. Random number generator only generating one random number
  • Failing to dispose the Pen object you create. This can lead to exhausting of GDI handles, which might be the cause of the apparent hang (and would explain why the program eventually does come back…if and when the finalizer thread catches up with all the undisposed Pen objects, the handles can be reclaimed and the program can proceed normally, for a little while longer until it runs out of handles again).
  • Using the static Form.ActiveForm property to get the form to draw into. This is wrong for at least two reasons: 1) it could draw into a window you don't intend to, and 2) there might not even be an active form, causing the property to return null, which then will lead to an exception.

All of the above can be solved easily, just by following the correct guidelines for writing Windows Forms programs:

  • Only ever draw in a Paint event handler or in the OnPaint() override. Call Invalidate() to cause the Paint event to be raised.
  • DoEvents() often is a crutch used by people who put long-running code into the UI thread, when that long-running code belongs in a different thread. Your code doesn't run long enough to block the UI thread for significant amounts of time, so it's not clear why that's even in your code. It can just be removed.
  • Create a single Random object once, and reuse it each time you want a new random number.
  • Dispose the Pen object. Use the using statement to do this safely and reliably.
  • Just use the current Form instance to decide what form to affect (in a correct version of the code, this means calling Invalidate() on the current instance, rather than calling CreateGraphics(), but the same principle applies regardless).

Here is a version of the code you posted that is correct. As a happy benefit, it also runs a lot faster. It can actually keep up with the 100ms interval that's the default for the System.Windows.Forms.Timer class.

private readonly Random randomColor = new Random();

public Form1()
{
    InitializeComponent();
    tmrStep.Tick += tmrStep_Tick;
    tmrStep.Start();
}

private void tmrStep_Tick(object sender, EventArgs e)
{
    Invalidate();
}

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    Graphics g = e.Graphics;

    Color defaultColor = Color.FromArgb(randomColor.Next(255), randomColor.Next(255), randomColor.Next(255));
    using (Pen defaultPen = new Pen(defaultColor, 2))
    {
        int intHeight = this.ClientSize.Height;
        int intWidth = this.ClientSize.Width;

        int intMiddleHeight = intHeight / 2;
        int intMiddleWidth = intWidth / 2;

        Point posTopLeft = new Point(0, 0);
        Point posTopRight = new Point(intWidth, 0);
        Point posBottomLeft = new Point(0, intHeight);
        Point posBottomRight = new Point(intWidth, intHeight);
        Point posMiddle = new Point(intMiddleWidth, intMiddleHeight);
        Point posMiddleTop = new Point(intMiddleWidth, 0);
        Point posMiddleLeft = new Point(0, intMiddleHeight);
        Point posMiddleRight = new Point(intWidth, intMiddleHeight);
        Point posMiddleBottom = new Point(intMiddleWidth, intHeight);

        int defaultStep = randomColor.Next(3, 10);

        int b1;
        for (b1 = 0; b1 <= intMiddleWidth; b1 += defaultStep)
        {
            g.DrawLine(defaultPen, posTopLeft.X + b1, posTopLeft.Y, posMiddle.X - b1, posMiddle.Y);
            g.DrawLine(defaultPen, posTopRight.X - b1, posTopRight.Y, posMiddle.X + b1, posMiddle.Y);
            g.DrawLine(defaultPen, posMiddle.X - b1, posMiddle.Y, posBottomLeft.X + b1, posBottomLeft.Y);
            g.DrawLine(defaultPen, posMiddle.X + b1, posMiddle.Y, posBottomRight.X - b1, posBottomRight.Y);
        }

        int b2;
        for (b2 = 0; b2 <= intMiddleHeight; b2 += defaultStep)
        {
            g.DrawLine(defaultPen, posMiddleTop.X, posMiddleTop.Y + b2, posMiddleLeft.X, posMiddleLeft.Y - b2);
            g.DrawLine(defaultPen, posMiddleTop.X, posMiddleTop.Y + b2, posMiddleRight.X, posMiddleRight.Y - b2);
            g.DrawLine(defaultPen, posMiddleRight.X, posMiddleRight.Y + b2, posMiddleBottom.X, posMiddleBottom.Y - b2);
            g.DrawLine(defaultPen, posMiddleLeft.X, posMiddleLeft.Y + b2, posMiddleBottom.X, posMiddleBottom.Y - b2);
        }
    }
}
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136