简体   繁体   中英

Passing parameters to the Event handler

Background: For a school assignment, I am making a puzzle game. The player has to create a puzzle game and save it in a text file. First, the player enters rows and columns of tiles (PictureBoxes), the program creates the 2d PictureBox layout. After which player selects the tool (button assigned an image from ImageList) and clicks on the tile, the image appears on a tile.

Approach: I have a custom class that inherits from PictureBox class and has property ToolValue. The ToolValue is the tool (constant assigned to tool) chosen by the player and added to that PictureBox. For loading the image, I have created a new event handler that handles the click event of PictureBox control and has additional parameters, inside the for-loop. The new event handler loads the image on the tile and sets the ToolValue property of the custom PictureBox(MyPictureBox) class to the constant. I have created a two-dimensional array of PictureBox class references.

MyPictureBox[,] Tile;

        public void DrawALineOfPictureBoxes(int rowNumber, int columnCount, int rowCount, int leftPosition, int topPosition, int height, int width)
        {
            Tile = new MyPictureBox[rowCount, columnCount];
            for (int colNumber = 0; colNumber < columnCount; colNumber++)
            {
                Tile[rowNumber, colNumber] = new MyPictureBox();
                Tile[rowNumber, colNumber].Left = leftPosition;
                Tile[rowNumber, colNumber].Top = topPosition;
                Tile[rowNumber, colNumber].Height = height;
                Tile[rowNumber, colNumber].Width = width;
                Tile[rowNumber, colNumber].BorderStyle = BorderStyle.Fixed3D;
                Tile[rowNumber, colNumber].SizeMode = PictureBoxSizeMode.StretchImage;
                Tile[rowNumber, colNumber].ToolValue = 0;
                Tile[rowNumber, colNumber].Click += new EventHandler((sender,e) => LoadImage_Click(sender, e, rowNumber, colNumber));
                this.Controls.Add(Tile[rowNumber, colNumber]);

                leftPosition += width;
            }
        }
        /// <summary>
        /// Method executes when Generate Button is clicked  
        /// Method invokes DrawALineOfPictureBoxes method which generates a row of Picture boxes
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// 
        private void PictureBoxGenerate_Click(object sender, EventArgs e)
        {
            try
            {
                int numRows = int.Parse(txtRowCount.Text);
                int numColumns = int.Parse(txtColumnCount.Text);
                int leftPos = 400;
                int topPos = 120;
                int height = 100;
                int width = 100;
                //loop after each row of picturebox is generated
                for (int rowNumber = 0; rowNumber < numRows; ++rowNumber)
                {
                    DrawALineOfPictureBoxes(rowNumber, numColumns, numRows, leftPos, topPos, height, width);
                    topPos += height;
                }
            }
            catch (FormatException)
            {
                MessageBox.Show("Please provide valid data for rows and columns (Both must be integers)","Sokoban", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }


        /// <summary>
        /// Method is executed when PictureBox cell is clicked and load image to that picture box through resources
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LoadImage_Click(object sender, EventArgs e, int rowNumber, int colNumber)
        {
            string Content = rowNumber.ToString() + "," + colNumber.ToString();
            Console.WriteLine(Content);
            MyPictureBox pictureBox = sender as MyPictureBox;
            switch (imageType)
            {
                case ImageType.None:
                    //Empty the pictureBox Cell
                    pictureBox.Image = null;
                    ToolVal = 0;

                    break;
                case ImageType.Hero:
                    pictureBox.Image = Properties.Resources.Hero;
                    ToolVal = 1;
                    break;
                case ImageType.Wall:
                    pictureBox.Image = Properties.Resources.Wall;
                    ToolVal = 2;
                    break;
                case ImageType.Box:
                    pictureBox.Image = Properties.Resources.Box;
                    ToolVal = 3;
                    break;
                case ImageType.Destination:
                    pictureBox.Image = Properties.Resources.Destination;
                    ToolVal = 4;
                    break;
                default:
                    break;
            }
            //assigning values to Tile array
            Tile[rowNumber,colNumber].ToolValue = ToolVal;
        }

Problem: I have passed row and column number of pictureBox which is clicked as a parameter to EventHandler that loads the image. Inside Event Handler, switch loop checks which tool is clicked and assign constant to toolValue property accordingly. If I am clicking on the first picturebox, the parameters passed to the event handler is 0 for rowNumber and 0 for colNumber. The event handler parameters should be the same but the colNumber argument is not 0, it is 3 ie. the total columns of 2d pictureBoxes generated.

Heres the output>> Colnumber is 3

Your problem is in the following line:

Tile[rowNumber, colNumber].Click += new EventHandler((sender,e) => LoadImage_Click(sender, e, rowNumber, colNumber));

You are using a lambda expression that passes rowNumber & colNumber. When the Click event for the tile is triggered, the lambda is executed and passes the values rowNumber & colNumber have at that time (and not the values they had when the event-handler was created). because your loop have run to completion, the value for colNumber you pass will always be 3.

So you need another mechanism to calculate the row & column for the clicked Tile. You could do that bases on the sender that is passed: cast it to a Tile, retrieve its position & calculate row & column from that.

If you really want to pass the actual column-number, you could always 'capture the value for 'colNumber' like this:

Tile = new MyPictureBox[rowCount, columnCount];
        for (int colNumber = 0; colNumber < columnCount; colNumber++)
        {
            int actualColumn = colNumber;
            int actualRow = rowNumber; 

            Tile[rowNumber, colNumber] = new MyPictureBox();
            ...
            Tile[rowNumber, colNumber].Click += new EventHandler((sender,e) => LoadImage_Click(sender, e, actualRow, actualColumn));
            this.Controls.Add(Tile[rowNumber, colNumber]);

            leftPosition += width;
        }

For each iteration of the loop where you create your tiles, you create an new variable that contains the actual Row & Column for your tile. Those values will not change, because every iteration has its own version of them. And you pass those variables instead of rowNumber & colNumber.

I can give you mostly general Advice:

First of all, WindowsForms and other GUI technologies are not the right tool for game development. It works for purely turnbased, single or hotseat multiplayer games. If you do not over the Graphics. Basically the old Solitair was about the upper end of what is possible. For serious development, you need something with the Game Loop. This is a school/learning project, so it might actually a bit to complex of a project.

Secondly, avoid storing data in the UI. Always have the data (the game state) in the code behind in some collection. The UI is meerely a representation of that data, that you regulary update.

As for Arguments handed into Events, the one most usefull here should be the sender . This is the instance that raised the event. You can make checks on this as is:

//Shared handler for OK, Apply and Cancel Buttons
if(sender == btnOK || sender == btnApply){

}
if(sender == btnOK || sender == btnCancel){

}

Or you can cast it, to get full access to the senders properties

PictureBox source = (PictureBox)sender;
source.ImageLocataion = "clicked.png";

One particular property of note is Tag . It takes a object and it's purpose seems to be to identify wich UI element you got here, if the name/reference alone does not work. Something like the tile number/Identifier should help here. Or the Primary key of a Database Row.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM