question

NicholasPiazza-0093 avatar image
0 Votes"
NicholasPiazza-0093 asked DanielZhang-MSFT answered

Problem with Windows.Forms.Timer and menu items interaction

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?


windows-forms
· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Maybe NewGeneration and UpdateGrid have issues or infinite loops. Show some details.


0 Votes 0 ·

In trying to show you the code for NewGeneration(), CheckStatus(), and UpdateGrid() in this reply I ran into the 1000 character limit, so see the Answer below for that code.

0 Votes 0 ·
Viorel-1 avatar image Viorel-1 NicholasPiazza-0093 ·

Did yo check if the menu works if you temporarily comment some parts of GenerationTimer_Tick? Which function seems to block the UI?

0 Votes 0 ·
NicholasPiazza-0093 avatar image
0 Votes"
NicholasPiazza-0093 answered NicholasPiazza-0093 commented
     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
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


What is the difference between myGrid and grid?

Maybe you can paint the entire grid using a single call of PanelGrid.Invalidate() instead of multiple PanelGrid_Paint?


0 Votes 0 ·

It seems to be the timer itself that is blocking input from the UI menu items. In GenerationTimer_Tick(), I have the problem whether I comment out the call to NewGeneration() or the call to UpdateGrid() or both. The grid, is setup in a method called SetupGame, which only has two lines:
grid = new bool [Columns, Rows];
game = new Game (Columns, Rows, cellWidth, grid);

As you can see, the initial grid is passed in the Game constructor, which assigns it to MyGrid in the Game class. Each call to NewGeneration() creates a temporary grid called newGrid in which to store the changes caused by the generation. Those changes are then copied back to MyGrid which is returned by NewGeneration() and assigned back to grid. UpdateGrid() then causes a Paint event to update the onscreen grid changes.

FYI, in an Answer box, I have also posted the Paint event handler that updates the onscreen grid and the SetupGrid method.

0 Votes 0 ·
NicholasPiazza-0093 avatar image
0 Votes"
NicholasPiazza-0093 answered

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


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DanielZhang-MSFT avatar image
0 Votes"
DanielZhang-MSFT answered

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.


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.