Problem with Windows.Forms.Timer and menu items interaction

Nicholas Piazza 541 Reputation points
2021-06-11T22:04:57.48+00:00

I would like help with a problem I'm having with System.Windows.Forms.Timer and menu item interactions. In the designer I have dragged in a Windows.Forms.Timer, which I use in a Game of Life implementation to determine when to switch to the next generation. I have menu subitems called Start, Stop, Pause, and Resume. I use the Start menu item to start the timer going. I wish to use Pause to temporarily stop the timer, Resume to resume running the timer, and Stop when I am done with the timer. The problem is: After I start the timer running, the UI no longer responds to the menu item events to Stop, Pause, or Resume. Shown below is the MenuItemClick event handler. The case "&Start" works, but none of the other cases are even entered once the timer is running. A breakpoint on the "switch (sender.ToString()) line is never hit after Start.

        private void MenuItemClick (object sender, EventArgs e)
        {
            switch (sender.ToString ())
            {
                case "&Start":
                    generation = 0;

                    generationTimer.Interval = 1000;
                    generationTimer.Start ();

                    startToolStripMenuItem.Enabled = false;
                    stopToolStripMenuItem.Enabled  = true;
                    pauseToolStripMenuItem.Enabled = true;
                    break;
                case "Sto&p":
                    generationTimer.Stop ();
                    generationTimer.Dispose ();

                    startToolStripMenuItem.Enabled = true;
                    stopToolStripMenuItem.Enabled  = false;
                    break;
                case "&Pause":
                    generationTimer.Stop ();

                    pauseToolStripMenuItem.Enabled  = false;
                    resumeToolStripMenuItem.Enabled = true;
                    break;
                case "&Resume":
                    generationTimer.Start ();

                    resumeToolStripMenuItem.Enabled = false;
                    pauseToolStripMenuItem.Enabled  = true;
                    break;
            }
        }

Shown below is the Timer Tick event handler.

        private void GenerationTimer_Tick (object sender, EventArgs e)
        {
            generation++;

            grid = game.NewGeneration ();

            UpdateGrid ();
        }

What am I doing wrong? What must I do to have the UI respond to menu item clicks after Start?

Developer technologies | Windows Forms
{count} votes

3 answers

Sort by: Most helpful
  1. Nicholas Piazza 541 Reputation points
    2021-06-12T13:25:33.797+00:00
        public bool [,] NewGeneration ()
        {
            // Set up a new working grid
            bool [,] newGrid = new bool [myColumns, myRows];
    
            // Scan the current grid.  For each cell in the current grid,
            // get a count of the live cells surrounding it
            for (int column = 0; column < myGrid.GetLength (0); column++)
            {
                for (int row = 0; row < myGrid.GetLength (1); row++)
                {
                    int count = CheckStatus (column, row);
    
                    // Based on the live cells surrounding the current cell
                    // use the Rules of Life to determine whether the current
                    // cell lives or dies
                    if (myGrid [column, row])
                    {
                        if (count == 2 || count == 3)
                            newGrid [column, row] = true;
    
                        if (count < 2 || count > 3)
                            newGrid [column, row] = false;
                    }
                    else
                    {
                        if (count == 3)
                            newGrid [column, row] = true;
                    }
                }
            }
            myGrid = newGrid;
    
            return newGrid;   // Update the main grid
    
        } // method NewGeneration
    

    NewGeneration() uses a method called CheckStatus() shown below.

        private int CheckStatus (int column, int row)
        {
            int count = 0;
    
            // if upper-left is alive...
            if ((column - 1 >= 0 && row - 1 > 0) &&
                myGrid [column - 1, row - 1] == true)
                count++;
    
            // if upper is alive...
            if ((column - 1 >= 0) && myGrid [column - 1, row] == true)
                count++;
    
            // if upper-right is alive...
            if ((column - 1 >= 0 && row + 1 < myRows) &&
                myGrid [column - 1, row + 1] == true)
                count++;
    
            // if left is alive...
            if ((row - 1 >= 0) && myGrid [column, row - 1] == true)
                count++;
    
            // if right is alive...
            if ((row + 1 < myRows) && myGrid [column, row + 1] == true)
                count++;
    
            // if lower-left is alive...
            if ((column + 1 < myColumns && row - 1 >= 0) &&
                myGrid [column + 1, row - 1] == true)
                count++;
    
            // if lower is alive...
            if ((column + 1 < myColumns) && myGrid [column + 1, row] == true)
                count++;
    
            // if lower-right is alive...
            if ((column + 1 < myColumns &&
                row + 1 < myRows) &&
                myGrid [column + 1, row + 1] == true)
                count++;
    
            return count;
    
        } // method CheckStatus
    

    Here is the code for UpdateGrid():

            private void UpdateGrid ()
            {
                var brushBlack = new SolidBrush (Color.Black);
                var brushWhite = new SolidBrush (SystemColors.Control);
    
                Graphics cellGraphics = panelGrid.CreateGraphics ();
    
                for (int i = 0; i < grid.GetUpperBound (0); i++)
                    for (int j = 0; j < grid.GetUpperBound (1); j++)
                    {
                        if (grid [i,j])
                            brush = brushBlack;
                        else
                            brush = brushWhite;
    
                        Rectangle rect = cells [i, j];
    
                        PanelGrid_Paint (this, new PaintEventArgs (cellGraphics, rect));
                    }
                brushBlack.Dispose ();
                brushWhite.Dispose ();
            }
    

  2. Nicholas Piazza 541 Reputation points
    2021-06-12T17:38:23.57+00:00

    Here is the Paint event handler for updating the onscreen gride.

            private void PanelGrid_Paint (object sender, PaintEventArgs e)
            {
                // If the grid has already been drawn, then
                // we only need to handle filling the grid square
                // with black (create a life entity) or white (kill a life entity)
                if (gridDrawn)
                {
                    blackPen = new Pen (Color.Black,1);
    
                    e.Graphics.FillRectangle (brush,    e.ClipRectangle);
    
                    // After filling the rectangle (especially with white),
                    // we need to redraw the rectangle, some of whose sides
                    // may have been overwritten by the brush.
                    e.Graphics.DrawRectangle (blackPen, e.ClipRectangle);
                    blackPen.Dispose ();
                }
                // If the grid has not yet been drawn (beginning of game)...
                else
                {
                    //  Set up the grid, mark it as drawn,
                    //  and set up the game to be played.
                    SetupGrid (e);
                    SetupGame ();
                }
            } // event handler PanelGrid_Paint
    

    and here is the SetupGrid() method

            private void SetupGrid (PaintEventArgs e)
            {
                // Compute and save the number of columns and rows
                Columns = panelGrid.Width / cellWidth;
                Rows    = panelGrid.Height / cellWidth;
    
                // Instantiate a black pen to draw the grid
                blackPen = new Pen (Color.Black,1);
    
                // Create the array of Rectangle's to hold grid squares
                cells = new Rectangle [Columns, Rows];
    
                // Create and draw the grid squares
                for (int i = 0; i < Columns; i++)
                {
                    for (int j = 0; j < Rows; j++)
                    {
                        cells [i,j] = new Rectangle (i * cellWidth, 
                                                     j * cellWidth, 
                                                     cellWidth, 
                                                     cellWidth);
    
                        e.Graphics.DrawRectangle (blackPen, cells [i, j]);
                    }
                }
                blackPen.Dispose ();
    
                gridDrawn = true;
    
            } // method SetupGrid
    
    0 comments No comments

  3. Daniel Zhang-MSFT 9,651 Reputation points
    2021-06-14T06:27:47.9+00:00

    Hi NicholasPiazza-0093,
    The System.Windows.Forms.Timer tick event executes on the UI thread. If the UI thread is busy doing something else, then the timer tick handler will have to wait.
    So I suggest you try to use System.Timers.Timer which happened on threadpool threads.
    Or try the BackgroundWorker which executes an operation on a separate thread.
    Here is a related thread you can refer to.
    Best Regards,
    Daniel Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.