I've been trying to create connect 4 (the game) in windows forms applications in C# but I'm kinda struggling to create a method so the coins actually drop and not stay were the user clicks. Also I don't know where should I place the win condition method. Here is my code so far:
namespace ConnectFour
{
public partial class Form1 : Form
{
Button[] gameButtons = new Button[42]; //array of buttons for markers(red and blue)
bool blue = true; //blue is set to true if the next marker is to be a blue
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.Text = "Connect 4";
this.BackColor = Color.BlanchedAlmond;
this.Width = 500;
this.Height = 500;
for (int i = 0; i < gameButtons.Length; i++)
{
int index = i;
this.gameButtons[i] = new Button();
int x = 50 + (i % 7) * 50;
int y = 50 + (i / 7) * 50;
this.gameButtons[i].Location = new System.Drawing.Point(x, y);
this.gameButtons[i].Name = "btn" + (index + 1);
this.gameButtons[i].Size = new System.Drawing.Size(50, 50);
this.gameButtons[i].TabIndex = i;
//this.gameButtons[i].Text = Convert.ToString(index);
this.gameButtons[i].UseVisualStyleBackColor = true;
this.gameButtons[i].Visible = true;
gameButtons[i].Click += (sender1, ex) => this.buttonHasBeenPressed(sender1, index);
this.Controls.Add(gameButtons[i]);
}
}
private void buttonHasBeenPressed(object sender, int i)
{
if (((Button)sender).BackColor == Color.BlanchedAlmond)
{
if (blue == true)
{
((Button)sender).BackColor = Color.Red;
}
else
{
((Button)sender).BackColor = Color.Blue;
}
blue = !blue;
}
}
private void fourInARow(int a, int b, int c,int d)
{
if (gameButtons[a].BackColor == gameButtons[b].BackColor && gameButtons[a].BackColor == gameButtons[c].BackColor && gameButtons[a].BackColor==gameButtons[d].BackColor)
{
if (gameButtons[a].BackColor == Color.Blue)
{
MessageBox.Show("the winner is player 1");
}
else
{
MessageBox.Show("the winner is player 2");
}
}
}
}
}
You've probably figured it out by now, but what I would do is take a look at the BackColor of each Button below the one the user pressed until we either hit the bottom row or we find a Button that doesn't have a "BlanchedAlmond" BackColor, and then that's the one we change.
To find a Button directly below another one, we just look at the Button with an Index of 7 more than the current Index. And since we pass the Button object's index in the index argument to our function, we can use that.
Here's a commented code example:
private void ButtonHasBeenPressed(object sender, int index)
{
// Get the button that the user pressed
var pressedButton = (Button)sender;
// Only do something if they clicked a "neutral" button
if (pressedButton.BackColor == Color.BlanchedAlmond)
{
// The backcolor will be set based on whether or not blue is true or false
var newBackColor = blue ? Color.Red : Color.Blue;
// Get the index of the button that the user clicked
var buttonToChangeIndex = index;
// From where the user clicked, look down at each button below (index + 7)
// until we find the last button that has a BlanchedAlmond backcolor
while (buttonToChangeIndex + 7 < gameButtons.Count() &&
gameButtons[buttonToChangeIndex + 7].BackColor == Color.BlanchedAlmond)
{
buttonToChangeIndex += 7; // Set to the index to point to this button
}
// Now we set that button's backcolor
gameButtons[buttonToChangeIndex].BackColor = newBackColor;
// Flip our blue flag
blue = !blue;
}
}
The fact that you use a one-dimensional array to hold the buttons makes things a little more difficult. Since the layout of this “connect four” game is two-dimensional, it would seem logical to make a two-dimensional array. This will help facilitate the “Drop” in a column. Your current code will need to translate which cells in the one-dimensional array make up a column, namely the one the user clicked.
The code below solves this “Dropping” issue by using a two-dimensional array with a method that gets the ROW index of the next available space in a given column. This method returns the next available open slot index in the given column starting at the bottom and going up or returns -1 if the column is full.
You should seriously consider making a class of tokens/markers/buttons, then another Class that is a game board for these tokens. The logic to see if there are 4 consecutive colors horizontally, vertically and diagonally could possibly be complex. However, this logic would fit perfectly towards a GameBoard Object. In addition, the “GameBoard” object could keep track of all the moves etc…
This will greatly free up your code to focus on the visual aspects of the form and not worry about the logic of the game. I hope this makes sense.
Button[,] gameButtons = new Button[7,6]; //array of buttons for markers(red and blue)
bool blue = true; //blue is set to true if the next marker is to be a blue
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
this.Text = "Connect 4";
this.BackColor = Color.BlanchedAlmond;
this.Width = 500;
this.Height = 500;
int x;
int y;
for (int row = 0; row < gameButtons.GetLength(0); row++) {
x = 50 + (row % 7) * 50;
for (int col = 0; col < gameButtons.GetLength(1); col++) {
y = 50*col + 50;
Button newButton = new Button();
newButton.Location = new System.Drawing.Point(x, y);
newButton.Name = "btn" + (row + col + 1);
newButton.Size = new System.Drawing.Size(50, 50);
newButton.TabIndex = row + col;
newButton.UseVisualStyleBackColor = true;
newButton.Visible = true;
newButton.Click += (sender1, ex) => this.buttonHasBeenPressed(sender1);
gameButtons[row, col] = newButton;
Controls.Add(gameButtons[row,col]);
}
}
}
private void buttonHasBeenPressed(object sender) {
Button buttonClicked = (Button)sender;
int col = buttonClicked.Location.X / 50 - 1;
int targetRow = GetButtonRow(col);
if (targetRow >= 0) {
if (blue == true) {
gameButtons[col, targetRow].BackColor = Color.Red;
} else {
gameButtons[col, targetRow].BackColor = Color.Blue;
}
blue = !blue;
}
}
public int GetButtonRow(int colIndex) {
Button curButton;
for (int row = gameButtons.GetLength(1) - 1; row >= 0; row--) {
curButton = gameButtons[arrayColIndex, row];
if (curButton.BackColor != Color.Red && curButton.BackColor != Color.Blue) {
return row;
}
}
return -1;
}
Related
I am currently having trouble with my slide puzzle. The thing is that the codes work but only for the 9 buttons that appeared out of no where. Here is an image of it.
This is the code I used to run the program.
public partial class frmSlide : Form
{
/*
* frmSlide -constructor
*
*/
DateTime TimedSolved = DateTime.Now;
bool starting = false;
bool changed = false;
const int rows = 3;
const int cols = 3;
Button blkButton= new Button();
public Button[,] btnArray = new Button[rows, cols];
Button[,] buttons = new Button[3, 3];
public frmSlide()
{
InitializeComponent();
}
/*
* allNumberButton_Click - click event handler for allNumberButton
*
* Parameter: sender -who sent the event
* Parameter: e -info about the event
* Return: nothing
*/
private void frmSlide_Load(object sender, EventArgs e)
{
blkButton.Text = " ";
int curNum = 1;
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
Button b;
b = new Button();
b.Location = new Point(100 + (y * 50), 100 + (x * 50));
b.Height = 40;
b.Width = 40;
b.Text = curNum.ToString();
btnArray[x, y] = b;
b.TextAlign = ContentAlignment.MiddleCenter;
b.Font = new Font(FontFamily.GenericSerif, (float)15.0);
b.Click += new System.EventHandler(this.allNumberButton_Click);
this.Controls.Add(b);
curNum++;
}
}
btnArray[rows - 1, cols - 1].Text = "";
blkButton = btnArray[rows - 1, cols - 1];
blkButton.Text = " ";
}
private void allNumberButton_Click(object sender, EventArgs e)
{
{
Button clickedButton = sender as Button;
if (clickedButton.Text == "")
{
goto done;
}
Point clicked = clickedButton.Location;
int clickedX = clicked.X;
int clickedY = clicked.Y;
Point blank = blkButton.Location;
int blankX = blank.X;
int blankY = blank.Y;
int distanceX = Math.Abs(clickedX - blankX);
int distanceY = Math.Abs(clickedY - blankY);
changed = false;
if ((distanceX == 50 && distanceY == 0) ||
(distanceX == 0 && distanceY == 50))
{
changed = true;
string tmp = clickedButton.Text;
clickedButton.Text = "";
blkButton.Text = tmp;
blkButton = clickedButton;
}
if (changed == true)
{
if (btnArray[rows - 1, cols - 1].Text != "")
{
goto done;
}
int checkNum = 1;
for (int a = 0; a < rows; a++)
{
for (int b = 0; b < cols; b++)
{
if (a == rows - 1 && b == cols - 1)
{
goto done2;
}
if (btnArray[a, b].Text != checkNum.ToString())
{
goto done;
}
checkNum++;
}
}
done2:;
if (starting == false)
{
DateTime nowSolved = DateTime.Now;
TimeSpan lengthOfTime = nowSolved - TimedSolved;
double hours = lengthOfTime.TotalHours;
int hours2 = Convert.ToInt32(hours);
double minutes = lengthOfTime.TotalMinutes;
int minutes2 = Convert.ToInt32(minutes);
double seconds = lengthOfTime.TotalSeconds;
int seconds2 = Convert.ToInt32(seconds);
MessageBox.Show("Congratulations! \n \n It took you " + hours2 +
" hours " + minutes2 + " minutes " + seconds2 + " seconds \n \n"
+ " to solve it!");
}
else
{
return;
}
}
done:;
}
}
/*
* btnReset_Click - click event handler
* .text
* Parameter: sender -who sent the event
* Parameter: e -info about the event
* Return: nothing
*/
private void btnReset_Click(object sender, EventArgs e)
{
{
int reset = 1;
for (int c = 0; c < rows; c++)
{
for (int l = 0; l < cols; l++)
{
btnArray[c, l].Text = reset.ToString();
reset++;
btnArray[rows - 1, cols - 1].Text = "";
blkButton = btnArray[rows - 1, cols - 1];
}
}
}
}
/*
* btnExit_Click -click event handler
*
* Parameter: sender -who sent the event
* Parameter: e -info about the event
* Return: nothing
*/
private void btnExit_Click(object sender, EventArgs e)
{
this.Close();
}
/*
* frmSlide_Load- Load event handler
*
* Parameter: sender -who sent the event
* Parameter: e -info about the event
* Return: nothing
*/
private void btnStart_Click(object sender, EventArgs e)
{
starting = true;
Random rnd = new Random();
for (int z = 0; z < 100; z++)
{
int rndNumberX = rnd.Next(3);
int rndNumberY = rnd.Next(3);
Button random = btnArray[rndNumberX, rndNumberY];
random.PerformClick();
}
starting = false;
}
}
Can you help me fix this problem with a solution because it is hard for me to figure out what happened?
I thought it might be the code I entered in the form load.
You'll need to remove the generation of those buttons in your frmSlide_Load event handler.
You'll also need to assign your existing buttons to btnArray.
I'll demonstrate how to do that using object initializer.
Comment out all lines in frmSlide_Load
Add InitializeSliderButtons method
Add InitializeSliderButtons() line in frmSlide
Handle distanceX == 50 && distanceY == 0 related checks\logic in allNumberButton_Click. The are probably not correct values as those existing buttons look different in width\height\spacing.
public frmSlide()
{
InitializeComponent();
InitializeSliderButtons();
}
public void InitializeSliderButtons()
{
btnArray = new Button[,]
{
{
btn1,
btn2,
btn3
},
{
btn4,
btn5,
btn6
},
{
btn7,
btn8,
btnBlank
}
};
foreach (var button in btnArray)
{
button.Click += new EventHandler(this.allNumberButton_Click);
}
blkButton = btnArray[rows - 1, cols - 1];
blkButton.Text = " ";
}
private void frmSlide_Load(object sender, EventArgs e)
{
// blkButton.Text = " ";
// int curNum = 1;
// for (int x = 0; x < rows; x++)
// {
// for (int y = 0; y < cols; y++)
// {
// Button b;
// b = new Button();
// b.Location = new Point(100 + (y * 50), 100 + (x * 50));
// b.Height = 40;
// b.Width = 40;
// b.Text = curNum.ToString();
// btnArray[x, y] = b;
// b.TextAlign = ContentAlignment.MiddleCenter;
// b.Font = new Font(FontFamily.GenericSerif, (float)15.0);
// b.Click += new System.EventHandler(this.allNumberButton_Click);
// this.Controls.Add(b);
// curNum++;
// }
// }
// btnArray[rows - 1, cols - 1].Text = "";
// blkButton = btnArray[rows - 1, cols - 1];
// blkButton.Text = " ";
}
To determine your form button values, you'll look at the Location property of the Button on the designer (in the Properties window\view)
In the above screenshot, it's 170, 122 (X, Y). The button to the right of it would have to be 220, 122 (X+50, Y+0) for your current logic\check to work. You'll reflect on those locations and differences and update your logic\checks to whatever you determine it should be.
I must say you need to take break and sit back. You are making this FAR more complicated than it has to be. Remember… KISS (Keep It Super Simple).
From what you stated, you are given a form with nine (9) Button objects on the form (btn1, btn2…btn9). The buttons are displayed in a 3x3 array or 3 rows of 3 columns. Given this, it is not a bad idea as you started to make a 3x3 Button array to “hold” the buttons.
One thing that you need to “forget about” at this time is the “blank” button. To start, let’s just say it is another button, it is irrelevant at this point if it is “blank” or not. So, let us fill the 3x3 Button array using the “existing” Buttons on the form. In addition, since we want ALL the button’s Click event to fire the same event, we can go ahead and wire them up also. Something like…
public void InitializeSliderButtons() {
btnArray[0, 0] = btn1;
btnArray[0, 1] = btn2;
btnArray[0, 2] = btn3;
btnArray[1, 0] = btn4;
btnArray[1, 1] = btn5;
btnArray[1, 2] = btn6;
btnArray[2, 0] = btn7;
btnArray[2, 1] = btn8;
btnArray[2, 2] = btn9;
foreach (var button in btnArray) {
button.Click += new EventHandler(allNumberButton_Click);
}
}
Now we have the Buttons in a nice and neat 3x3 array. Next, we need to move to the buttons “Click” event allNumberButton_Click. When this event is fired, the first problem we have is figuring out “which” button was clicked. This is easily done, however, there is one button that we want to “ignore” if it is clicked and it doesn’t matter “which” button it is. That button would be the Button that has the text set to a blank string. In other words, if the blank button is clicked then we do not have to do anything. So, the first check in this event is to check for this blank button.
private void allNumberButton_Click(object sender, EventArgs e) {
Button clickedButton = (Button)sender;
if (clickedButton.Text == "")
return;
// if code execution gets here then the clicked button is NOT the blank button
}
After this if the code did not return, then we DO need to find out “which” Button was clicked. In this case the only way we can tell which button is clicked is by looking at the button name. We already know the button names are btn1, btn2… etc. However, EVEN if we know the button name, then we would still have to figure out “where” that button was in relation to the 3x3 button array. It is irrelevant what the button name is, we need the position in the button array. In other words, I only care about “which” button was clicked in relation to the 3x3 button array.
If we can get the clicked button's X,Y position in the btnArray, then it will make it trivial to check the buttons above, below, left and right of this button. If any of the above, below, left or right buttons are empty/blank, then we can switch the two button’s text value.
Given this, I will use a Point object as it has an X and Y property we can use. To help we will create a very simple method that takes a Button, and returns a Point. We will know the “position” of the button by its name. So btn1 would return Point(0,0), btn2 would return Pont(0,1)… etc. This will help in the click event by giving us the X and Y coordinates of the clicked button in relation to the 3x3 button array. If we are given a button that does not match any of the name’s we are looking for then we will return a -1, -1. This method may look something like…
private Point GetButtonPosition(Button button) {
if (button.Name == "btn1")
return new Point(0, 0);
if (button.Name == "btn2")
return new Point(0, 1);
if (button.Name == "btn3")
return new Point(0, 2);
if (button.Name == "btn4")
return new Point(1, 0);
if (button.Name == "btn5")
return new Point(1, 1);
if (button.Name == "btn6")
return new Point(1, 2);
if (button.Name == "btn7")
return new Point(2, 0);
if (button.Name == "btn8")
return new Point(2, 1);
if (button.Name == "btn9")
return new Point(2, 2);
return new Point(-1, -1);
}
This will update the click method like below…
private void allNumberButton_Click(object sender, EventArgs e) {
Button clickedButton = (Button)sender;
if (clickedButton.Text == "")
return;
Point buttonPosition = GetButtonPosition(clickedButton);
}
Now that we have the clicked buttons position in relation to the 3x3 all we need to do is check to see if one of the buttons above, below, left or right of this button is an “empty/blank” button. In this example, I have created another method that takes a Button and returns a Point object.
In this method, we will check buttons above, below, left and right of the given button and see if any of those buttons are the empty/blank button. If we find one of the buttons IS the empty/blank button, then, we will return the X, Y position in relation to the 3x3 array of that blank button. If none of the buttons above, below, left and right are the blank button, then we will return a -1, -1 to indicate that none of the button are the blank button. This method may look something like…
private Point GetBlankButtonNextToGivenButtonPosition(Point btnPosition) {
// check buttons to the left and right of the given button
if (btnPosition.X - 1 >= 0) {
if (btnArray[btnPosition.X - 1, btnPosition.Y].Text == "") {
return new Point(btnPosition.X - 1, btnPosition.Y);
}
}
if (btnPosition.X + 1 < 3) {
if (btnArray[btnPosition.X + 1, btnPosition.Y].Text == "") {
return new Point(btnPosition.X + 1, btnPosition.Y);
}
}
// Check for buttons above and below the given button
if (btnPosition.Y - 1 >= 0) {
if (btnArray[btnPosition.X, btnPosition.Y - 1].Text == "") {
return new Point(btnPosition.X, btnPosition.Y - 1);
}
}
if (btnPosition.Y + 1 < 3) {
if (btnArray[btnPosition.X, btnPosition.Y + 1].Text == "") {
return new Point(btnPosition.X, btnPosition.Y + 1);
}
}
// blank not found
return new Point(-1, -1);
}
With this, now we can check and see if any of the buttons above, below, right or left of the clicked button is the blank button. When we call the method above, IF we get a Point with values of -1, -1, then we know that we do NOT need to swap the button text values. On the other hand, if we get any other number then that would indicate that there IS a blank button next to the clicked button. With this we would have all the info we need to swap the text values. We have the position of the clicked button, and we also have the position of the blank button “next” to the clicked button. This should complete the Click event code…
private void allNumberButton_Click(object sender, EventArgs e) {
Button clickedButton = (Button)sender;
if (clickedButton.Text == "")
return;
Point buttonPosition = GetButtonPosition(clickedButton);
if (buttonPosition.X != -1) {
Point blankPosition = GetBlankButtonNextToGivenButtonPosition(buttonPosition);
if (blankPosition.X != -1) {
btnArray[blankPosition.X, blankPosition.Y].Text = clickedButton.Text;
clickedButton.Text = "";
}
}
if (BoardIsCorrect()) {
MessageBox.Show("You solved the puzzle!");
}
}
I have another method called BoardIsCorrect to check and see if the numbers are in the correct sequence and if so, return true. This and the start and reset methods are also added below.
public Button[,] btnArray = new Button[3, 3];
Random rand = new Random();
private void frmSlide_Load(object sender, EventArgs e) {
InitializeSliderButtons();
}
private bool BoardIsCorrect() {
if (btnArray[0, 0].Text == "1" &&
btnArray[0, 1].Text == "2" &&
btnArray[0, 2].Text == "3" &&
btnArray[1, 0].Text == "4" &&
btnArray[1, 1].Text == "5" &&
btnArray[1, 2].Text == "6" &&
btnArray[2, 0].Text == "7" &&
btnArray[2, 1].Text == "8"
) {
return true;
}
return false;
}
private void btnStart_Click(object sender, EventArgs e) {
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int nextRand;
int randIndex;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
randIndex = rand.Next(0, numbers.Count);
nextRand = numbers[randIndex];
if (nextRand != 9) {
btnArray[i, j].Text = nextRand.ToString();
}
else {
btnArray[i, j].Text = "";
}
numbers.RemoveAt(randIndex);
}
}
}
private void btnReset_Click(object sender, EventArgs e) {
int rows = 3;
int cols = 3;
int count = 1;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
btnArray[r, c].Text = count++.ToString();
}
}
btnArray[2, 2].Text = "";
}
I hope this make sense.
I am having a bit of a small issue performing a certain task in my winforms application.
I am basically attempting to recreate a "Top-View RTS Map", on a winform. In order to save memory, not all tiles of the "Map" are displayed on the screen. Only the ones that fit within the viewport. Therefore, I am trying to allow the user to perform a pan/scroll on the displayed tiles in order to navigate the entire map!
Right now, I am doing this by creating and displaying GroupBox controls dynamically at runtime. These represent the tiles...
I have created my own objects to support all of this (contains screen coordinates, Row and Col info, etc.)
Here is how I am currently accomplishing all of this, in pseudo-code:
Creating the form, tiles and the map in general
I create a winforms form that is 600px X 600px.
I create a new "Map" (using a List<MapTile>) that is 100 tiles by 100 tiles (for testing) on form load and save that into a variable.
I keep track of the displayed tiles via another list (or property that derives from the main list bool MapTile.isDrawn)
Each tile is visually made of a GroupBox control that is 100px
X 100px (so [7 X 7] of them fit on screen)
To start, I find the center MapTile (tile [50, 50]) in the "Map", create the GroupBox for it and place that in the middle of the form,
I then add the other tiles/controls necessary to fill in the form (center - 3 tiles, center + 3 tiles (up, down, left, and right)).
Each tile, of course, subscribes to the proper mouse events to perform a drag
When the user mouse drags a tile, all other tiles being displayed follow suit/follow the leader by updating all "displayed tiles" coordinates to match the movement that was made by the "dragged" tile.
Managing Displayed Tiles
While the GroupBox tiles are being dragged/moved, I perform a check to see if the tiles that are on the outer edge of the viewport are within its bounds.
If, as an example, the top-left-most tile's right edge falls outside the bounds of the left edge of the viewport, I remove the entire left column tiles, and add the entire right column tiles programmatically. The same goes all directions (up, down, left and right).
So far, this works fine as long as I don't go too fast... however, when I drag the tiles "too fast" passed an outer edge (e.g.: where point 2 ci-dessus would apply), it seems that the application is unable to keep up because it doesn't add the column or row where they should be on the form, and other times, it does not have time to remove all controls of a row or column and I end up with controls that are still on the screen when they shouldn't be there. At that point the entire grid/map is off balance and stops working as intended because either the events that should fire on one edge don't (the tiles are not present) and/or there are now multiple controls with the same name on the form and the removal or referencing fails...
While I am well aware that winforms is not designed to perform intensive GPU/GDI operations, you would think that something this simple would still be easily do-able in winforms?
How would I go about making this more responsive at runtime? Here's my entire set of code:
Form code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RTSAttempt
{
public enum DrawChange
{
None,
Rem_First_Draw_Last,
Rem_Last_Draw_First
};
public partial class Form1 : Form
{
public string selected { get; set; }
private int _xPos { get; set; }
private int _yPos { get; set; }
private bool _dragging { get; set; }
public List<MapTile> mapTiles { get; set; }
public List<MapTile> drawnTiles { get { return this.mapTiles.Where(a => a.Drawn == true).ToList(); } }
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//init globals
this.selected = "";
this._dragging = false;
this.mapTiles = new List<MapTile>();
//for testing, let's do 100 x 100 map
for (int i = 0; i < 100; i++)
{
for (int x = 0; x < 100; x++)
{
MapTile tile = new MapTile(x, i, false, -1, -1, false);
this.mapTiles.Add(tile);
}
}
GenerateStartupTiles();
}
/// <summary>
/// Used to generate the first set of map tiles on screen and dispaly them.
/// </summary>
private void GenerateStartupTiles()
{
//find center tile based on list size
double center = Math.Sqrt(this.mapTiles.Count);
//if not an even number of map tiles, we take the next one after the root.
if (this.mapTiles.Count % 2 != 0)
center += 1;
//now that we have the root, we divide by 2 to get the true center tile.
center = center / 2;
//get range of tiles to display...
int startat = (int)center - 3;
int endat = (int)center + 3;
//because the screen is roughly 600 by 600, we can display 7 X 7 tiles...
for (int row = 0; row < 7; row++)
{
for (int col = 0; col < 7; col++)
{
//get the current tile we are trying to display.
MapTile tile = mapTiles.First(a => a.Row == (startat + row) && a.Col == (startat + col));
//create and define the GroupBox control we use to display the tile on screen.
GroupBox pct = new GroupBox();
pct.Width = 100;
pct.Height = 100;
//find start position on screen
if (row == 0)
pct.Top = -50;
else
pct.Top = -50 + (row * 100);
if (col == 0)
pct.Left = -50;
else
pct.Left = -50 + (col * 100);
tile.X = pct.Left;
tile.Y = pct.Top;
pct.Name = tile.ID;
pct.Tag = Color.LightGray;
//subscribe to necessary events.
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = tile.DisplayID;
//add the tile to the screen
this.Controls.Add(pct);
//set the tile to Drawn mode...
tile.Drawn = true;
}
}
}
private void Pct_MouseUp(object sender, MouseEventArgs e)
{
//self explanatory
if (this._dragging)
{
Cursor.Current = Cursors.Default;
this._dragging = false;
}
}
private void Pct_MouseMove(object sender, MouseEventArgs e)
{
var c = sender as GroupBox;
if (!_dragging || null == c) return;
//get original position, and movement step/distance for calcs.
int newTop = e.Y + c.Top - _yPos;
int newLeft = e.X + c.Left - _xPos;
int movedByX = this.drawnTiles.First(a => a.ID.ToString() == c.Name).X;
int movedByY = this.drawnTiles.First(a => a.ID.ToString() == c.Name).Y;
movedByY = newTop - movedByY;
movedByX = newLeft - movedByX;
//perform all tile movements here
MoveAllTiles(movedByX, movedByY);
}
/// <summary>
/// This method performs all tile movements on screen, and updates the listing properly.
/// </summary>
/// <param name="X">int - the amount fo pixels that the dragged tile has moved horizontally</param>
/// <param name="Y">int - the amount fo pixels that the dragged tile has moved vertically</param>
private void MoveAllTiles(int X, int Y)
{
//used to single out the operation, if any, that we need to do after this move (remove row or col, from edges)
DrawChange colAction = DrawChange.None;
DrawChange rowAction = DrawChange.None;
//move all tiles currently being displayed first...
for (int i = 0; i < this.drawnTiles.Count; i++)
{
//first, determine new coordinates of tile.
drawnTiles[i].Y = drawnTiles[i].Y + Y;
drawnTiles[i].X = drawnTiles[i].X + X;
//find the control
GroupBox tmp = this.Controls.Find(drawnTiles[i].ID, true)[0] as GroupBox;
//perform screen move
tmp.Top = drawnTiles[i].Y;
tmp.Left = drawnTiles[i].X;
tmp.Refresh();
}
//dtermine which action to perform, if any...
if (drawnTiles.Last().Y > this.Height)
rowAction = DrawChange.Rem_Last_Draw_First;
else if ((drawnTiles.First().Y + 100) < 0)
rowAction = DrawChange.Rem_First_Draw_Last;
else
rowAction = DrawChange.None;
if ((drawnTiles.First().X + 100) < 0)
colAction = DrawChange.Rem_First_Draw_Last;
else if (drawnTiles.Last().X > this.Width)
colAction = DrawChange.Rem_Last_Draw_First;
else
colAction = DrawChange.None;
//get currently dispalyed tile range.
int startRow = this.drawnTiles.First().Row;
int startCol = this.drawnTiles.First().Col;
int endRow = this.drawnTiles.Last().Row;
int endCol = this.drawnTiles.Last().Col;
//perform the correct action(s), if necessary.
if (rowAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first row of tiles from the screen
this.drawnTiles.Where(a => a.Row == startRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last row of tiles on screen...
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == endRow + 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.Last().Y + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (rowAction == DrawChange.Rem_Last_Draw_First)
{
//remove last row of tiles
this.drawnTiles.Where(a => a.Row == endRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first row of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == startRow - 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.First().Y - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
if (colAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first column of tiles
this.drawnTiles.Where(a => a.Col == startCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == endCol + 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.Last().X + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (colAction == DrawChange.Rem_Last_Draw_First)
{
//remove last column of tiles
this.drawnTiles.Where(a => a.Col == endCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == startCol - 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.First().X - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
ToolTip tt = new ToolTip();
tt.SetToolTip(pct, pct.Name);
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
}
private void Pct_MouseDown(object sender, MouseEventArgs e)
{
//self explanatory
if (e.Button != MouseButtons.Left) return;
_dragging = true;
_xPos = e.X;
_yPos = e.Y;
}
private void Pct_Click(object sender, EventArgs e)
{
//changes the border color to reflect the selected tile...
if (!String.IsNullOrWhiteSpace(selected))
{
if (this.Controls.Find(selected, true).Length > 0)
{
GroupBox tmp = this.Controls.Find(selected, true)[0] as GroupBox;
ControlPaint.DrawBorder(tmp.CreateGraphics(), tmp.ClientRectangle, Color.LightGray, ButtonBorderStyle.Solid);
}
}
GroupBox pct = sender as GroupBox;
ControlPaint.DrawBorder(pct.CreateGraphics(), pct.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
this.selected = pct.Name;
}
private void Pct_Paint(object sender, PaintEventArgs e)
{
//draws the border based on the correct tag.
GroupBox pct = sender as GroupBox;
Color clr = (Color)pct.Tag;
ControlPaint.DrawBorder(e.Graphics, pct.ClientRectangle, clr, ButtonBorderStyle.Solid);
}
private void Pct_MouseLeave(object sender, EventArgs e)
{
//draws the border back to gray, only if this is not the selected tile...
GroupBox pct = sender as GroupBox;
if (this.selected != pct.Name)
{
pct.Tag = Color.LightGray;
pct.Refresh();
}
}
private void Pct_MouseEnter(object sender, EventArgs e)
{
//draws a red border around the tile to show which tile the mouse is currently hovering on...
GroupBox pct = sender as GroupBox;
pct.Tag = Color.Red;
pct.Refresh();
}
}
}
MapTile object
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RTSAttempt
{
public class MapTile
{
/// <summary>
/// Represents the row of the tile on the map
/// </summary>
public int Row { get; set; }
/// <summary>
/// Represents the column of the tile on the map
/// </summary>
public int Col { get; set; }
/// <summary>
/// Represents the ID of this tile ([-1,-1], [0,0], [1,1], etc
/// </summary>
public string ID { get { return "Tile_" + this.Row + "_" + this.Col; } }
public string DisplayID { get { return this.Row + ", " + this.Col; } }
/// <summary>
/// If this tile is currently selected or clicked.
/// </summary>
public bool Selected { get; set; }
/// <summary>
/// Represents the X screen coordinates of the tile
/// </summary>
public int X { get; set; }
/// <summary>
/// Represents the Y screen coordinates of the tile
/// </summary>
public int Y { get; set; }
/// <summary>
/// Represents whether this tile is currently being drawn on the screen.
/// </summary>
public bool Drawn { get; set; }
public MapTile(int idCol = -1, int idRow = -1, bool selected = false, int screenX = -1, int screenY = -1, bool drawn = false)
{
this.Col = idCol;
this.Row = idRow;
this.Selected = selected;
this.X = screenX;
this.Y = screenY;
this.Drawn = drawn;
}
public override bool Equals(object obj)
{
MapTile tmp = obj as MapTile;
if (tmp == null)
return false;
return this.ID == tmp.ID;
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
}
I'd create the grid using (DataGridView, TableLayoutPanel, GDI+, or whatever) and then in the drag and drop, just calculate the new indexes and update the indexes, without moving the grid.
Example
The following example shows how to do it using a TableLayoutPanel:
Assign a fixed size to cells
Build the grid to fill the form
When form resizes, rebuild the grid
In mouse down capture the mouse down point and current top left index of grid
In mouse move, calculate the new index based on mouse movement and update index
In cell paint of the panel, draw the indexes
Here is the code:
int topIndex = 0, leftIndex = 0;
int originalLeftIndex = 0, originalTopIndex = 0;
int cellSize = 100;
Point p1;
TableLayoutPanel panel;
void LayoutGrid()
{
panel.SuspendLayout();
var columns = (ClientSize.Width / cellSize) + 1;
var rows = (ClientSize.Height / cellSize) + 1;
panel.RowCount = rows;
panel.ColumnCount = columns;
panel.ColumnStyles.Clear();
panel.RowStyles.Clear();
for (int i = 0; i < columns; i++)
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, cellSize));
for (int i = 0; i < rows; i++)
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, cellSize));
panel.Width = columns * cellSize;
panel.Height = rows * cellSize;
panel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
panel.ResumeLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
panel = new MyGrid();
this.Controls.Add(panel);
LayoutGrid();
panel.MouseDown += Panel_MouseDown;
panel.MouseMove += Panel_MouseMove;
panel.CellPaint += Panel_CellPaint;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (panel != null)
LayoutGrid();
}
private void Panel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
var g = e.Graphics;
TextRenderer.DrawText(g, $"({e.Column + leftIndex}, {e.Row + topIndex})",
panel.Font, e.CellBounds, panel.ForeColor);
}
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var dx = (e.Location.X - p1.X) / cellSize;
var dy = (e.Location.Y - p1.Y) / cellSize;
leftIndex = originalLeftIndex - dx;
topIndex = originalTopIndex - dy;
panel.Invalidate();
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
p1 = e.Location;
originalLeftIndex = leftIndex;
originalTopIndex = topIndex;
}
To prevent flicker:
public class MyGrid : TableLayoutPanel
{
public MyGrid()
{
DoubleBuffered = true;
}
}
So, for anyone trying to do this, as a concept, here is how to fix this issue:
Instead of only drawing 1 row/col extra outside the viewport to save memory, draw the entire viewport's worth of cells in every direction of the edges (up, down, left and right)... for example, if your viewport can hold 5 tiles (5 X 5 = 25), then you need to draw 5 X 5 outside the viewport in every other direction (25 X 4 = 100)...
When the mouse is being dragged, just move the controls that are already on the form/control/"drawn"... this way, the user cannot, while dragging, go outside the bounds of the existing tiles... example, if they reach the outer right edge with the mouse, while dragging the left-most tile, the tiles to show on the left already exist! So we are just "following the mouse", which is not an issue if the controls are already there/there is no "loss/issues" because we are not removing or adding any tiles at this point...
When the user stops dragging the selected tile around (onMouseUp), THEN we re-calculate the tiles that need to be drawn and those that don't... so we only redraw (add and/or remove controls where necessary) the entire set of "drawn" tiles after the user is done dragging...
With this method, you remove any "missplaced" controls, double generation of controls, controls missing and any other issue that arises when the mouse moves too fast for the "Calculate drawn tiles" code to execute. You also "see" the map moving around as you drag, and you always have the correct tiles drawn on the screen! Problem solved!
However, I did find that when I used a UserControl instead of the form itself, the controls draw and update much faster and better than if I just add them to the form itself... therefore, I have accepted the answer that outlines that aspect as the actual answer, and placed this here for anyone else in future that might wonder how to do this as a concept.
This function dynamically creates nine buttons for use in a game I am making. You can see what attributes I give to the button.
private void createbuttons()
{
int tot = 0;
int x = 100;
int y = 100;
while(tot < 9)
{
string buttonsname = (tot + "button").ToString();
Button creating = new Button();
creating.Name = buttonsname;
creating.Size = new Size(100, 100);
creating.Click += delegate
{
MessageBox.Show("You clicked me!");
};
creating.Text = buttonsname;
if(x > 300)
{
y += 100;
x = 100;
}
creating.Location = new Point(x, y);
Controls.Add(creating);
tot += 1;
x += 100;
}
}
What I want to know is how to reference these buttons in different parts of the same form. Specifically when 'Start Game' is clicked I want to change the text for each button to something different.
private void button10_Click(object sender, EventArgs e)
{
//What would I write here to change the text?
}
You can access the buttons by enumerating the controls, or you could create a list of buttons for future reference, and use that list later.
Here is how you do it with a list:
private IList<Button> addedButtons = new List<Button>();
private void createbuttons() {
int tot = 0;
int x = 100;
int y = 100;
while(tot < 9) {
string buttonsname = (tot + "button").ToString();
Button creating = new Button();
creating.Name = buttonsname;
creating.Size = new Size(100, 100);
creating.Click += delegate {
MessageBox.Show("You clicked me!");
};
creating.Text = buttonsname;
if(x > 300) {
y += 100;
x = 100;
}
creating.Location = new Point(x, y);
addedButtons.Add(creating); // Save the button for future reference
Controls.Add(creating);
tot += 1;
x += 100;
}
}
Now you can do this:
foreach (var btn : addedButtons) {
btn.Text = "Changed "+btn.Text;
}
The form has a property Controls that holds all child controls. To find a child control by its Name property use method Find, which returns the array, because there may be several control with the same Name, but if you make sure that names exist, are unique, and you know their type (Button) you can just take the first item from the array and cast it:
private void button10_Click(object sender, EventArgs e)
{
Button buttonNamedFred = (Button)this.Controls.Find("Fred", false)[0];
buttonNamedFred.Text = "I'm Fred";
}
I'm experimenting with a form that contains some vertically stacked panels and I'm trying to implement some drag and drop so they can be re-ordered. It's working well so far apart from one bug that I can't figure out.
The premise is that it's a list of files which must be kept vertically stacked, but can be re-ordered as the user sees fit, to form a list of pages for a document I'll produce later.
Try the following code and you should get a form with 6 stacked panels. Click and drag any one of them up or down and they will re-order.
The problem is that if you drag one up and down really fast they start to overlap with each other.
I've put the shortcut CTRL + B in there to list the current panel top positions at any given time and you'll see that you get duplicate numbers when you shouldn't.
The problem is with the MouseMove event. I'm sure it's something like a race condition where the dictionary can't keep up or the indexes don't calculate fast enough but I'm scratching my head. I've tried locking them but it hasn't helped.
I'm sure there's a better way to implement this so I'm open to suggestions, but given that this will never have more than say 30 panels this suits my needs.
Apologies for the messy-ness, I was planning to tidy up later!
public Form1()
{
InitializeComponent();
this.SuspendLayout();
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 601);
this.DoubleBuffered = true;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MaximumSize = new System.Drawing.Size(300, 640);
this.MinimumSize = new System.Drawing.Size(300, 640);
this.Name = "Form1";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
this.ResumeLayout(false);
}
Dictionary<int, Panel> panelPositions = new Dictionary<int, Panel>();
static Random rnd = new Random();
int y;
int start;
static int index, previndex, currentindex, newindex, maxindex;
bool isDragging;
Panel currentPanel;
static readonly object Lock = new Object();
private void Form1_Load(object sender, EventArgs e)
{
Width = 300;
Height = 640;
int count = 5;
int currentTop = 0;
for (int i = 0; i <= count; i++)
{
Panel panel = new Panel();
panel.Width = 300;
panel.Height = 100;
panel.Top = currentTop;
panel.BackColor = RandomColor();
panel.Margin = Padding.Empty;
panel.Padding = Padding.Empty;
panel.BorderStyle = BorderStyle.None;
Label label = new Label();
label.Font = new Font("Segoe UI", 24.0f);
label.Text = (i + 1).ToString();
label.Top = 20;
label.Left = 20;
label.AutoSize = true;
panel.Controls.Add(label);
panel.MouseDown += new MouseEventHandler(MouseisDown);
panel.MouseMove += new MouseEventHandler(MouseMoves);
panel.MouseUp += new MouseEventHandler(MouseisUp);
lock (Lock)
{
panelPositions.Add(i, panel);
}
Controls.Add(panel);
currentTop += 100;
}
lock (Lock)
{
maxindex = panelPositions.Count - 1;
}
}
private void MouseisUp(object sender, MouseEventArgs e)
{
if (isDragging)
{
if (newindex < maxindex)
{
currentPanel.Top = newindex * 100;
}
else
{
currentPanel.Top = maxindex * 100;
}
}
isDragging = false;
}
// I'M SURE THE PROBLEM IS IN HERE SOMEWHERE.
private void MouseMoves(object sender, MouseEventArgs e)
{
// CHECK THE MOUSE IS STILL DOWN
if (isDragging)
{
// DRAG PANEL VERTICALLY WITH MOUSE
currentPanel.Location = new Point(currentPanel.Left, e.Y + currentPanel.Top - y);
// WORK OUT NEW INDEX POSITION
newindex = 0;
if ((currentPanel.Top + e.Y) > 0)
{
newindex = ((currentPanel.Top + e.Y) / 100);
}
// NEW POSITION?
if (currentindex != newindex)
{
// TRACK CHANGES
previndex = currentindex;
currentindex = newindex;
/* PRETTY SURE IT'S THIS BIT THAT'S WRONG */
// CHECK WE'RE NOT OUT OF BOUNDS
if (currentindex <= maxindex)
{
lock (Lock)
{
// RE-ARRANGE PANEL INDEX
panelPositions[previndex] = panelPositions[currentindex];
panelPositions[currentindex] = currentPanel;
panelPositions[previndex].Top = previndex * 100;
}
}
}
}
}
private void MouseisDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
currentPanel = sender as Panel;
isDragging = true;
currentPanel.BringToFront();
y = e.Y;
start = currentPanel.Top;
int i = 0;
if (currentPanel.Top > 0)
{
i = currentPanel.Top;
i = (i / 100);
}
index = i;
previndex = i;
currentindex = i;
}
else
{
isDragging = false;
}
}
private Color RandomColor()
{
Color randColor;
randColor = Color.FromArgb(RandomRGB(), RandomRGB(), RandomRGB());
return randColor;
}
private int RandomRGB()
{
return rnd.Next(1, 256);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.B)
{
string message = string.Empty;
int count = panelPositions.Count;
for (int i = 0; i < count; i++)
{
message += #"Panel " + i.ToString() + #": " + panelPositions[i].Top.ToString() + Environment.NewLine;
}
MessageBox.Show(message);
}
}
Thanks!
Edit: I think it's something to do with when the panel goes outside the bottom bounds of the form. If you drag within the form the issue doesn't occur. I think it's to do with the previous index, but I can't see it.
Change
// NEW POSITION?
if (currentindex != newindex)
To
// NEW POSITION?
if (currentindex != newindex && newindex <= maxindex)
I think you know why :)
P.s: Your "Edit" section is a bit misleading.
I'm having a matrix of buttons created in the main grid in a window, and also created the event for each one.
I also have an integer matrix that contains some properties of each button(e.g. int a[1,2] is property for button btn[1,2]).
I am planning to create something like a maze on which you can only pass from one button to another by leaping in the knight(from chess) way.I dont know how to find the coordinates of the button pressed so that I may change the position of the current button.
Button[,] btn = new Button[25, 25];
for (x = 5; x <= n; x++)
{
for (y = 5; y <= n; y++)
{
btn[x, y] = new Button();
left += 72;
btn[x,y].Margin =new Thickness(left,top,0,0);
btn[x,y].Height = 32;
btn[x,y].Width = 32;
btn[x, y].Click += new RoutedEventHandler(btn_Click);
if (a[x, y] == 2)
btn[x,y].Background = Brushes.Red;
else
btn[x,y].Background = Brushes.Blue;
main.Children.Add(btn[x, y]);
}
left = 0;
top += 72;
}
}
private void btn_Click(object sender, RoutedEventArgs e)
{
}
I just go a crazy idea... Why not make your own Button and call it MazeButton, or something?
Derive from Button and add some properties, make use of inheritance.
public class MazeButton : System.Windows.Controls.Button {
private int left;
private int top;
//rest of implementation
}
In that way you can pass the information where the button in your maze resides directly into the button. You can define custom events and anything else you desire.
Try this:
private void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Buttonl;
if(btn!=null)
{
Point renderedLocation = btn.TranslatePoint(new Point(0, 0), this);
}
else
{
//you may throw an exception if you want.
}
}
this is another approch of the problem. I store each button in a List of Tuple instead of a array, then i search wich tuple contains the button with Linq.
This is assuming you are in .NET 4. If not, the class Tuple can be written (you can find it on SO).
private List<Tuple<Button, Int32, Int32>> listButton;
private void SetButtons()
{
// TODO define what is n, left, top
listButton = new List<Tuple<Button, int, int>>();
for (int x = 5; x <= n; x++)
{
for (int y = 5; y <= n; y++)
{
Button btn = new Button();
left += 72;
btn.Margin = new Thickness(left, top, 0, 0);
btn.Height = 32;
btn.Width = 32;
btn.Click += new RoutedEventHandler(btn_Click);
if (a[x, y] == 2)
btn.Background = Brushes.Red;
else
btn.Background = Brushes.Blue;
listButton.Add(new Tuple<Button, int, int>(btn, x, y));
main.Children.Add(btn);
}
left = 0;
top += 72;
}
}
private void btn_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
var tuple = listButton.Where(t => t.Item1 == button).FirstOrDefault();
if (tuple != null)
{
Int32 x = tuple.Item2;
Int32 y = tuple.Item3;
// Do whay you want this x and y found
}
}