I am trying to make a simple five in a row (gomoku) game for two players using windows forms and c#. I put a picturebox with a picture and stretched it out on the form. Now I want to put labels at all the intersections on the picture board so a user can click them and change their background color to black or white.
How can I make the labels created clickable on the form?
public partial class Form1 : Form
{
int labelCount = 0;
int iteration = 0;
public Form1()
{
InitializeComponent();
Label[] board = new Label[361];
for (int i = 0; i < 361; i++)
{
board[i] = new Label
{
Name = "label" + i,
Height = 55,
Width = 55,
MinimumSize = new Size(55, 55),
Text = "label " + i
};
}
int x = 0;
int y = 0;
foreach (var Label in board)
{
if (x >= 580)
{
x = 0;
y = y + Label.Height + 55;
}
Label.Location = new Point(x, y);
this.Controls.Add(Label);
x += Label.Width;
}
}
}
Should I make a one-dimensional [361] or two-dimensional array[{A,1}, {A,2}....{D,1}] to easily check for a winner? How can I connect it to the created labels so the array data corresponds to the objects on the board?
Well Sorry If don`t understand your question. For the Q.1 to add 361 labels you can try the code below. I hope it will help you.
public int x = 0;
public int y = 0;
private Label[] moku = new Label[361];
private void Form1_Load(object sender, EventArgs e)
{
try
{
for (int i = 0; i < 361; i++)
{
moku[i] = new Label();
moku[i].Parent = pictureBox1;//make the picturebox parent
moku[i].Location = new Point(x, y);
moku[i].Text = "O";
moku[i].Name = "moku" + i;
moku[i].BackColor = Color.Transparent;
pictureBox1.Controls.Add(moku[i]);
y += 55;
if (y >= 361) { x += 55; y = 0; x+=55; }
}
}catch(Exception er)
{
MessageBox.Show(er.ToString());
}
}
I prefer using a 2D array because it's easier if you want to check the surrounding boxes.
Form design:
Full source:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public enum Player
{
Empty = 0,
White,
Black
}
public partial class Form1 : Form
{
// initialize board of 5x5
private Player[,] board = new Player[5, 5];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
DrawBoard();
}
private void DrawBoard()
{
for (var i = 0; i <= board.GetUpperBound(0); i++)
{
for (var j = 0; j <= board.GetUpperBound(1); j++)
{
// for name and text
var name = string.Format("{0}, {1}", i, j);
var label = new Label()
{
Name = name, // name of label
Size = new Size(55, 55),
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(i * 55, j * 55), // location depends on iteration
Text = name
};
label.Click += ClickLabel; // subscribe the Click event handler
pictureBox1.Controls.Add(label); // add label to a container
}
}
}
// this event handler will handle all the labels click event
private void ClickLabel(object sender, EventArgs e)
{
var label = (Label)sender; // this is the label that you click
var x = Convert.ToInt32(label.Name.Split(',')[0]);
var y = Convert.ToInt32(label.Name.Split(',')[1]);
// change the color
if (radPlayerBlack.Checked)
{
// Player Black
label.ForeColor = Color.White;
label.BackColor = Color.Black;
board[x, y] = Player.Black;
}
else
{
// Player White
label.ForeColor = Color.Black;
label.BackColor = Color.White;
board[x, y] = Player.White;
}
}
}
}
You can check the value of the 2D array for black or white. Here's the value when I QuickWatch it in Visual Studio.
Related
I have a dictionary of buttons and also an null variable button. When you click on an button from the dictionary, we get the values that should be sent to an empty variable.
public static Button selectedfigure=null;
public Dictionary<(int x, int y), Button> _Panels = new Dictionary<(int x, int y), Button>();
public void Fillboard()
{
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
_Panels[(x, y)] = new Button()
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y)
};
Controls.Add(_Panels[(x, y)]);
}
}
}
Your question is about how to interact with Mouse events on your "board". The answer is to subscribe to Mouse events like Click for each control you add. Your code is using a normal Button but will be much simpler if you make a custom class that inherits Button or preferably PictureBox and this way each instance keeps track of its own X-Y, color, and image.
Also, instead of using a Dictionary lookup and setting Location properties yourself, you might try an 8x8 TableLayoutPanel for the board, then iterate all its 8 columns and rows with methods that take column and row as arguments.
The mouse events can be subscribed to in the addSquare method.
private void addSquare(int column, int row)
{
var color = ((column + row) % 2) == 0 ? Color.White : Color.Black;
var square = new Square
{
BackColor = color,
Column = column,
Row = row,
Size = new Size(80, 80),
Margin = new Padding(0),
Padding = new Padding(10),
Anchor = (AnchorStyles)0xf,
SizeMode = PictureBoxSizeMode.StretchImage,
};
tableLayoutPanel.Controls.Add(square, column, row);
// Hook the mouse events here
square.Click += onSquareClicked;
square.MouseHover += onSquareMouseHover;
}
Before and after iterating the TableLayoutPanel with addSquare
Changing the value of the Square.Piece property will choose an image from a resource file.
private void initImage(int column, int row)
{
var square = (Square)tableLayoutPanel.GetControlFromPosition(column, row);
if (square.BackColor == Color.Black)
{
switch (row)
{
case 0:
case 1:
case 2:
square.Piece = Piece.Black;
break;
case 5:
case 6:
case 7:
square.Piece = Piece.Red;
break;
default:
square.Piece = Piece.None;
break;
}
}
}
Before and after iterating the TableLayoutPanel with initImage
Mouse event handlers are simple now:
private void onSquareMouseHover(object sender, EventArgs e)
{
var square = (Square)sender;
_tt.SetToolTip(square, square.ToString());
}
private void onSquareClicked(object sender, EventArgs e)
{
var square = (Square)sender;
MessageBox.Show($"Clicked: {square}");
}
ToolTip _tt = new ToolTip();
The Square class is uncomplicated, just a few lines of code.
class Square : PictureBox // Gives more visual control than Button
{
public int Column { get; internal set; }
public int Row { get; internal set; }
Piece _piece = Piece.None;
public Piece Piece
{
get => _piece;
set
{
if(!Equals(_piece, value))
{
_piece = value;
switch (_piece)
{
case Piece.None:
Image = null;
break;
case Piece.Black:
Image = Resources.black;
break;
case Piece.Red:
Image = Resources.red;
break;
}
}
}
}
public override string ToString() =>
Piece == Piece.None ?
$"Empty {BackColor.Name} square [column:{Column} row:{Row}]" :
$"{Piece} piece [column:{Column} row:{Row}]";
}
enum Piece { None, Black, Red };
Edited (in response to Jeremy's excellent suggestion)
Where and how to call the methods to initialize the board:
public MainForm()
{
InitializeComponent();
// Board is a TableLayoutPanel 8 x 8
tableLayoutPanel.AutoSize = true;
tableLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Add squares
IterateBoard(addSquare);
// Add pieces
IterateBoard(initImage);
}
void IterateBoard(Action<int, int> action)
{
for (int column = 0; column < 8; column++)
{
for (int row = 0; row < 8; row++)
{
action(column, row);
}
}
}
I don't know exactly which is your problem. I think you need know which button is pressed in your matrix.
You can add the controls in the same way but instead the use of _Panels dictionary, you can use Tag property of button to store a Point with the x,y coordinates:
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
var button = new Button
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y),
Tag = new Point(x, y)
};
button.Click += OnButtonClick;
Controls.Add(button);
}
}
In the click event handler of the button:
private void OnButtonClick(object sender, EventArgs e)
{
var button = (Button)sender;
var point = (Point)button.Tag;
// Here you have x,y coordinates of clicked button
}
I'm trying to make a little game where you turn all panels green.
I do this by getting a 5x5 grid of panels, every panel has a 1/3 chance to be green, otherwise it will start as red.
my problem is that i do not have the slightest clue how to start the main problem.
when i click a panel, the panel above, left ,right and below need to change color aswell.
at the moment i do not know how to identify which panels are next to the one being clicked.
this is my code:
public partial class Form1 : Form
{
Panel[,] PanelArray = new Panel[5,5];
Random R = new Random();
int R1;
public Form1()
{
InitializeComponent();
for (int r = 0; r < 5; r++)
{
for (int c = 0; c < 5; c++)
{
R1 = R.Next(0, 3);
PanelArray[r, c] = new Panel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D
};
PanelArray[r, c].Click += new EventHandler(PanelArray_Click);
if (R1 == 1) PanelArray[r, c].BackColor = Color.Green;
PanelContainer.Controls.Add(PanelArray[r, c]);
}
}
}
private void PanelArray_Click(object sender, EventArgs e)
{
Panel P = (Panel)sender;
if (P.BackColor == Color.Red) P.BackColor = Color.Green;
else if (P.BackColor == Color.Green) P.BackColor = Color.Red;
if (CheckWin()) MessageBox.Show("test");
}
private bool CheckWin()
{
//foreach panel green blah blah
return false;
}
}
}`
You can use the Tag property in your Panel objects to store some information.
PanelArray[r, c] = new Panel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D,
Tag = (Row: r, Column: c)
};
In your PanelArray_Click method, you can get the indexes:
var indexes = ((int Row, int Column))P.Tag;
var row = indexes.Row;
var column = indexes.Column;
// Todo: your logic here
In the Tag property, you can store any object, so you can create some class to store data, if you need.
Other solution is two for loops, to get the indexes, like:
private (int Row, int Column) GetIndexes(Panel panel)
{
for (int x = 0; x < PanelArray.GetLength(0); x++)
{
for (int y = 0; y < PanelArray.GetLength(1); y++)
{
if (PanelArray[x, y] == panel)
{
return (x, y);
}
}
}
throw new Exception("Not found.");
}
And then you can use in your PanelArray_Click method:
var indexes = this.GetIndexes(P);
var row = indexes.Row;
var column = indexes.Column;
// Todo: your logic here
Here is easy trick to get row and column. Create a class that inherits the Panel and adds a row and column. See code below
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
MyPanel[,] PanelArray = new MyPanel[5, 5];
Random R = new Random();
int R1;
public Form1()
{
InitializeComponent();
for (int r = 0; r < 5; r++)
{
for (int c = 0; c < 5; c++)
{
R1 = R.Next(0, 3);
PanelArray[r, c] = new MyPanel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D,
row = r,
col = c
};
PanelArray[r, c].Click += new EventHandler(PanelArray_Click);
if (R1 == 1) PanelArray[r, c].BackColor = Color.Green;
PanelContainer.Controls.Add(PanelArray[r, c]);
}
}
}
private void PanelArray_Click(object sender, EventArgs e)
{
MyPanel P = sender as MyPanel;
int row = P.row;
int col = P.col;
if (P.BackColor == Color.Red) P.BackColor = Color.Green;
else if (P.BackColor == Color.Green) P.BackColor = Color.Red;
if (CheckWin()) MessageBox.Show("test");
}
private bool CheckWin()
{
//foreach panel green blah blah
return false;
}
}
public class MyPanel : Panel
{
public int row { get; set; }
public int col { get; set; }
}
}
I would like to be able to create X x Y number of boxes / circles / buttons inside a picturebox. EXACTLY like Windows Defrag tool.
I tried to create a layout and keep adding buttons or pictureboxes to it, but its extremely slow and after 200 or so pictureboxes it crashes, runs out of window handles or memory.
What's the alternative? Can you show me a simple piece of code to add boxes just like Defrag tool where I can easily access them like
box[x,y].Color = Green as my app makes progress?
Currently I have this:
private void ResetTableStyles()
{
boardPanel.Controls.Clear();
boardPanel.RowStyles.Clear();
boardPanel.ColumnStyles.Clear();
boardPanel.RowCount = Rows;
boardPanel.ColumnCount = Columns;
for (int i = 0; i < Rows; i++)
{
boardPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100f));
}
for (int j = 0; j < Columns; j++)
{
boardPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f));
}
}
private void CreateButtons()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var button = new PictureBox
{
BackColor = Color.White,
Dock = DockStyle.Fill,
Margin = Padding.Empty,
Tag = new Point(i, j),
BackgroundImageLayout = ImageLayout.Stretch
};
//button.MouseDown += button_MouseDown;
boardPanel.Controls.Add(button, j, i);
}
}
}
which as I said doesn't work, after sometime it just crashes and it takes very long time.
Perhaps someone has a better answer, but you could use a Panel as the canvas and handle the Paint event to draw colored rectangles onto the Panel. You could then use the mouse events, such as MouseMove, to figure out which cell you're on.
Here is a super-simple proof of concept.
// Data.cs
namespace WindowsFormsApplication
{
public class Data
{
public int State { get; set; }
public string Tip { get; set; }
}
}
// Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var rng = new Random();
_data = new Data[30 * 30];
for (int i = 0; i < _data.Length; i++)
{
_data[i] = new Data
{
State = rng.Next(0, 3),
Tip = $"Data at index: {i}"
};
}
}
private Data[] _data;
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (var brush = new SolidBrush(Color.Gray))
using (var buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics,
new Rectangle(0, 0, 450, 450)))
{
for (int x = 0; x < 30; x++)
{
for (int y = 0; y < 30; y++)
{
int dataIdx = (y * 30) + x;
Data data = _data[dataIdx];
if (data.State == 1)
{
brush.Color = Color.Blue;
}
else if (data.State == 2)
{
brush.Color = Color.Red;
}
else
{
brush.Color = Color.Gray;
}
buffer.Graphics.FillRectangle(brush, x * 15, y * 15, 15, 15);
buffer.Graphics.DrawLine(Pens.Black, 0, y * 15, 450, y * 15); //Gridline
}
buffer.Graphics.DrawLine(Pens.Black, x * 15, 0, x * 15, 450); //Gridline
}
buffer.Render(e.Graphics);
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
var point = e.Location;
int x = point.X / 15;
int y = point.Y / 15;
int dataIdx = (y * 30) + x;
System.Diagnostics.Debug.WriteLine(_data[dataIdx].Tip);
}
}
}
The Data class represents the model behind each segment on the panel and is pretty straight forward.
Form1 has a single control placed on it: a Panel named panel1 with a size of 450 x 450. Each cell on the panel will be 15 x 15, so we have 30 columns and 30 rows. In Form1's constructor, we initialize the Data that will be used to draw the panel.
Inside of Form1.panel1_Paint we iterate through the cells and look up the instance of Data for the given cell. Then, based on the State, set a color for the cell and draw a rectangle of that color. This is done on a buffer so the panel doesn't flicker when painting.
Inside of Form1.panel1_MouseMove we use the location of the mouse pointer relative to panel1 to figure out which cell the mouse is over, get the instance of Data for that cell, and display the Tip in the debug window.
You might be able to take this and build upon it, perhaps with a custom UserControl so you can easily access each individual cell with the column and row... your box[x, y].Color = Colors.Green example above.
I have FlowLayoutPanel with
AutoScroll = True
FlowDirection = LeftToRight
WrapContents = True
Added controls dynamically have same Width but AutoSize in Height. So the panel will be like this, which has vertical spaces between items. As the height of row managed by the greatest height of controls. So I want to remove these unneeded spaces, and the final result will be like this.
If there's no way to do it with FlowLayoutPanel, What's the proper idea to done it perfectly ?
Its a matrix and should be treated like a matrix.
my opinion is that Panel is more appropriate than a FlowLayoutpanel here.
please see my suggestion and output to achieve such a behavior.
clarification: this code needs improvements to be adapted to all possible cases but you can learn from it the basic idea how to deal with such problem.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Example();
}
// space beetween controls (top and right)
public int MarginSpace = 8;
// first element location
public Point StartPoint = new Point(10, 10);
private void Example()
{
var fixesWidth = 70;
List<Label> randomLables = new List<Label>();
Random rand = new Random();
// generate lables with random heights
for (int i = 1; i < 10; i++)
{
Label lr = new Label();
var randheight = rand.Next(60, 120);
lr.Size = new Size(fixesWidth, randheight);
lr.Text = i.ToString();
lr.BackColor = Color.Black;
lr.ForeColor = Color.White;
randomLables.Add(lr);
}
// check how many elements in one "column" (possible also to add right+left margin)
var cols = panel1.Width / fixesWidth;
// create matrix object to get locations of each label
MyMatrix m = new MyMatrix(cols, randomLables.Count, 15, 70, StartPoint);
m.SetMatrix(randomLables);
int counter = 0;
// pupulate all lables with the points from MyMatrix object
foreach (Point p in m.pointsMatrix)
{
randomLables[counter].Location = p;
panel1.Controls.Add(randomLables[counter]);
counter++;
}
}
}
class MyMatrix
{
private int Rows;
private int TotalElements;
private int Cols;
private int Margin;
private int ElementWidth;
private Point StartPoint;
public MyMatrix(int cols, int totalelements, int margin, int elementwidth, Point startingpoint)
{
this.Cols = cols;
this.TotalElements = totalelements;
this.Margin = margin;
this.ElementWidth = elementwidth;
this.StartPoint = startingpoint;
// calculate number of rows
Rows = totalelements / cols;
}
public List<Point> pointsMatrix = new List<Point>();
int cellCounter = 0;
public void SetMatrix(List<Label> Labels)
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Cols; j++)
{
var x = StartPoint.X + j * (Margin + ElementWidth);
var y = StartPoint.Y;
if (cellCounter >= Cols)
{
// find the parallel cell in the row above
y = pointsMatrix[cellCounter - Cols].Y + Labels[cellCounter - Cols].Height + Margin;
}
else
{
// do nothing it is first row
}
Point p = new Point(x, y);
pointsMatrix.Add(p);
cellCounter += 1;
}
}
}
}
Output:
It's my problem.
I have a few RadioButtons. If i click on first Radiobutton on form create TextBox, if click on second - create second TextBox and if i click again on first RadioButton, again one TextBox,is it possible?
Give me idea, please.
And without property visible.
There isn't much point in dynamically creating and destroying controls here. It is just a headache, ensuring the position, size and tab order is correct. Just make the text box visible if you like the choice:
private void radioButton2_CheckedChanged(object sender, EventArgs e) {
textBox1.Visible = radioButton2.Checked;
}
Set the textbox' Visible property to False in the designer.
Try something like this (this is only one of them):
TextBox t;
private void radio_CheckedChanged(object sender, System.EventArgs e)
{
if (radio.Checked) {
t = new TextBox();
t.Top = radio.Top;
t.Left = radio.Left + radio.Width;
this.Controls.Add(t);
t.Show();
} else {
if (t!=null)t.Dispose();
}
}
See Only foreach and void TextBoxes.
using System;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
private const int
HeightTextBox = 40, WidthTextBox = 25, //размер textboxes
DistanceBetweenTexBoxHeight = 25, DistanceBetweenTexboxWigth = 25; //растояние между ними
private int DimentionalTextBox = 3;
private const int
RadioButtonNumbers = 3, // количество радио кнопок
DistanceBetweenRadiobutton = 50,
RadioButtonFirstGroupStartPositionX = 5,
RadioButtonSecondGroupStartPositionX = 0,
RadioButtonFirstGroupStartPositionY = 0,
RadioButtonSecondGroupStartPositionY = 0,
RadioButtonSize = 25;
public MyForm()
{
//Size of window
ClientSize = new System.Drawing.Size(7 * HeightTextBox + 8 * DistanceBetweenTexBoxHeight,
7 * WidthTextBox + 8 * DistanceBetweenTexboxWigth);
//Create RaioButton
int x = RadioButtonFirstGroupStartPositionX;
int y;
RadioButton[] DimRadioButtons = new RadioButton[RadioButtonNumbers];
for (int i = 0; i < RadioButtonNumbers; i++)
{
DimRadioButtons[i] = new RadioButton();
DimRadioButtons[i].Name = "RadioButton" + (i + 2);
DimRadioButtons[i].Text = Convert.ToString(i + 2);
DimRadioButtons[i].SetBounds(x, RadioButtonFirstGroupStartPositionY, RadioButtonSize, RadioButtonSize);
x += DistanceBetweenRadiobutton;
Controls.Add(DimRadioButtons[i]);
}
//Watch dimention
// And catch even click on RadioButton
foreach (var a in this.Controls)
{
if (a is RadioButton)
{
if (((RadioButton)a).Checked)
{
DimentionalTextBox = Convert.ToInt16(((RadioButton)a).Text);
((RadioButton)a).Click += new EventHandler(this.TextBoxes);
}
}
}
}
// Create-Delete TextBoxes
private void TextBoxes(object sender, EventArgs e)
{
RadioButton rb_click = (RadioButton)sender;
int x = RadioButtonFirstGroupStartPositionX;
int y = 30;
int dim = Convert.ToInt16(rb_click.Text);
TextBox[,] MatrixTextBoxes = new TextBox[dim, dim];
for (int i = 0; i < dim; i++)
{
for (int j = 0; j < dim; j++)
{
MatrixTextBoxes[i, j] = new TextBox();
MatrixTextBoxes[i, j].Top = rb_click.Top;
MatrixTextBoxes[i, j].Name = "MatrixTextBox" + i + j;
MatrixTextBoxes[i, j].Text = i + " " + j;
MatrixTextBoxes[i, j].SetBounds(x, y, WidthTextBox, HeightTextBox);
x += DistanceBetweenTexboxWigth;
this.Controls.Add(MatrixTextBoxes[i, j]);
MatrixTextBoxes[i, j].Show();
}
y += DistanceBetweenTexBoxHeight;
x = RadioButtonFirstGroupStartPositionX;
}
}
}
class MyClassMain : MyForm
{
public static void Main()
{
Application.Run(new MyClassMain());
}
}