Search code examples
c#asp.netwebforms

Button click causing a page refresh in WebForms


I'm trying to code a game, I got an ASP (4 by 4) table, and in each cell, there is a button.

When we run the program, on Page_Load(), each button gets a random number (displayed in its button.text). Part of the game's goal is clicking the buttons causing them to change their text, I managed to do this using a button.click method, my problem is that on each click, while the intended goal works and the button we clicked changes its text, the rest of the buttons reload and get new values for their button.text.

(ig that Page_Load is running again), any tips for why this is happening and what is the best way to deal with this to achieve my goal?

this is my main.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="main.aspx.cs" Inherits="HM1.main" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Table ID="GameTable" runat="server"></asp:Table>
        </div>
    </form>
</body>
</html>


and that is my main.aspx.cs (please note that everything in the for loop, is just the code to get buttons 1-15 randomly placed, and number 16 in the button right corner while being invisible)

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HM1
{
    public partial class main : System.Web.UI.Page
    {
        public Button[,] buttons;

        protected void Page_Load(object sender, EventArgs e)
        {
            buttons = new Button[4, 4];
            int[] buttonMumbers = new int[buttons.Length];
            for (int z=0; z < buttonMumbers.Length; z++)
            {
                buttonMumbers[z] = z + 1;
            }

            var rng = new Random();
            var indexs = buttonMumbers.Select(x => rng.NextDouble()).ToArray();
            Array.Sort(indexs, buttonMumbers);

            int counter = 0;
            for(int i = 0; i <buttons.GetLength(0); i++){
                var row = new TableRow();
                for(int j = 0; j<buttons.GetLength(1); j++){
                    var cell = new TableCell();
                    var button = buttons[i, j] = new Button();
                    button.Width = 75;
                    button.Height = 75;
                    button.BackColor = Color.DarkCyan;
                    button.Click += new EventHandler(this.BtnClick);

                    if(i == j && i==3)
                    {
                        break;
                    }
                    if (buttonMumbers[counter] == 16)
                    {
                        counter++;
                        button.Text = buttonMumbers[counter].ToString();
                    }else { 
                        button.Text = buttonMumbers[counter].ToString();
                        counter++;
                    } 
                    cell.Controls.Add(button);
                    row.Controls.Add(cell);
                }
                GameTable.Controls.Add(row);
            }
            buttons[3, 3].Visible = false;
        }

        public void BtnClick(Object sender, EventArgs e) {
            Button clickedButton = (Button)sender;
            clickedButton.Text = "clicked!";
        }
    }
}

Solution

  • The behavior's you are seeing is not only correct but is in fact by design.

    So, any button click on a web page will cause the page load event to run first, AND THEN your button code stub will run.

    So, the simple solution is to place such page load code that is to only run on the REAL first page load is to have/add/use this additional if () code in that page load event:

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                // the real first page load code goes here
                string strSQL = @"SELECT * FROM tblHotelsA
                              ORDER BY HotelName";
    
                GVHotels.DataSource = General.MyRst(strSQL);
                GVHotels.DataBind();
    
            }
        }
    

    So, in above, I load up a GridView with some data. If I don't place that code inside of the Is not post back if block, then the GridView would be re-loaded each and every time a button is clicked.

    This can be a problem say for a simple combo box. If you have a combo box (dropdown list) on a page, and on page load you load up the combo box with choices?

    Then that on-load code will run EVERY TIME and over write the user's choice in that combo box. So, once again, this means/suggest/shows/implies that while page load event can be freely used to load up and setup code and values?

    Just keep in mind that such code runs each and every time when a post-back occurs. Thus, it stands to reason that about 99% of your pages will need the above all important !IsPostBack stub.

    In fact, I am rather comfortable stating that you really can't much build a working webforms page without a !IsPostBack code stub. Since in near all cases, we want some page setup code to run one time, and only on the first real page load. If such setup code runs each time on page load, then such code will overwrite values of controls and objects you setup in that load event. And as noted, any combo box, check box, or even a button that triggers a post back means page load runs first, and then the code stub for the given control runs.

    So, the simple solution as noted is to use the above !IsPostBack code stub.

    It is perhaps relevant that I point out the reason for the above design in web forms as compared to desktop land. In web land, you are dealing with what we call a state-less system. That means that you have a so-called page life cycle, and without a grasp of this page life cycle you can't write software for the web!

    Unlike a desktop program in which each user has their own computer, CPU and memory? In web land, the code you write for that given page DOES NOT persist after the page been sent to the client side!

    In other words, you click a button. This starts the post back, and the whole web page is sent up to the server. The server determines what web page, and THEN CREATES the page class (the code behind class). The code runs, page is sent back to the client side, and THEN the page, the variables, the memory is disposed of!!!! (yes, the code is now blowen out of the memory). This means that the variables and values in that page are now removed from memory, and disposed of. Hence the term "state less".

    In effect, each button click (post back) means a WHOLE new instance of the code behind is re-created every time. Each user of the web site does not have a copy of such code in the web server's memory. And after one page is processed and set to client side, then the web server is now ready to process OTHER web pages from other users. So, there is ONLY one computer here unlike desktop software in which everyone has their own copy of the software.

    The above explains then why page load has to trigger each time, since often a lot of setup code will have to run each and every time, since each and every time the memory, the variables, and the code is re-run starting out from scratch each time. You thus can't and don't expect variables in the code behind module to persist after a post-back completes the so called round trip. Thus all values and variables and even the page class are thus disposed of, and thus all code and variables go out of scope.

    This is very much like what occurs when you exit a subroutine call. The local variables in that subroutine all are disposed and go out of scope when you exit (return) from that subroutine.

    Well, in web land, your WHOLE page class (code behind) goes out of scope EVERY time!

    So, often you will need and want some setup code in the page load even to run, but not code that only should run one time. But there will OFTEN be code that you want to run each time, since as noted, all the variables and values in that code are lost after each post-back.