How to interact with mouse in Windows Form - c#

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
}

Related

Connect 4 regardless of which cells is clicked on a grid find the bottom cell and change its image

All grid cells are white images which are initially set to 0 and when clicked are either set to 1 or 2 depending on the color.
I cant figure out how to make it so regardless of which white image the user clicks it will go to the bottom of that column, find the last cell with the value of 0 and fill that cell with the colored tile.
string[] imagePaths = { "empty.png", "Yellow.png", "Red.png" };
const int NumberOfCells = 36;
const int WidthOfBoard = 6;
//defualt player
string playersTurn = "Red";
int[] boardValues = new int[NumberOfCells];
ImageButton[] buttons = new ImageButton[NumberOfCells];
public MainPage()
{
InitializeComponent();
for (int i = 0; i < NumberOfCells; i++)
{
buttons[i] = (ImageButton)board.FindByName("cell" + i);
}
ResetGame();
Rules();
}
//put image of blue or red tile on grid and update turn
private async void Cell_Clicked(object sender, EventArgs e)
{
ImageButton button = (ImageButton)sender;
int y = (int)button.GetValue(Grid.ColumnProperty);
int x = (int)button.GetValue(Grid.RowProperty);
int index = (y * WidthOfBoard) + x;
if (boardValues[index] == 0)
{
{
button.TranslationY = -400;
buttons[index].Source = playersTurn + ".png";
if (playersTurn == "Yellow")
{
boardValues[index] = 1;
CheckForWinner();
playersTurn = "Red";
}
else
{
boardValues[index] = 2;
CheckForWinner();
playersTurn = "Yellow";
}
turnLabel.Text = playersTurn + "'s Turn";
await button.TranslateTo(0, 0, 250);
}
}
}
How the board looks

Dynamically creating dots/squares inside picturebox

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.

How to get position of a button in an array on mouse clicked

I have the code that creates a grid of buttons from an array. I need to get their possitions in array on mouse clicked event. Any ideas or links to some other post/articles would be very helpful.
The code for creating the grid:
// Creating buttons array for 5x5 grid
Button[] tiles25 = new Button[25];
// Generating 5x5 button grid
void Spawn5x5Grid()
{
// position of the firts tile
int x = 35, y = 55;
// current tile index
int count = 0;
for (int i = 1; i < 6; i++)
{
for (int j = 1; j < 6; j++)
{
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
count++;
x = x + 24;
}
x = 35;
y = y + 24;
}
lblSize.Text = "5 x 5";
currentGrid = Grids.grid5x5;
}
I suggest scanning tiles25 array in the Click event handler
...
Controls.Add(tiles25[count]);
tiles25[count].Click += (o, ee) => {
Button button = o as Button;
int index = Array.IndexOf(tiles25, button);
//TODO: Put relevant code here: "button" clicked which is at "index" position
};
count++;
x = x + 24;
...
Register an event handler for each of your button click events and then extract the Location property from the sender object:
// Generating 5x5 button grid
void Spawn5x5Grid()
{
// position of the firts tile
int x = 35, y = 55;
// current tile index
int count = 0;
for (int i = 1; i < 6; i++)
{
for (int j = 1; j < 6; j++)
{
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
tiles25[count].Click += Tiles25_Click;
count++;
x = x + 24;
}
x = 35;
y = y + 24;
}
lblSize.Text = "5 x 5";
currentGrid = Grids.grid5x5;
}
private void Tiles25_Click(object sender, EventArgs e)
{
var bt = sender as Button;
MessageBox.Show("X = " + bt.Location.X + "; Y = " + bt.Location.Y);
}
You need to set up an event handler for when the button is clicked. Right now, all you're doing is creating the buttons and adding them to the list of controls at a given position. Now, all you have to add is the event handler for the click event!
//...
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y),
};
tiles25[count] += new EventHandler(this.Tile_Click);
//...
void Tile_Click(Object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
//...
}
Then inside of the Tile_Click() event handler, you can use whatever code necessary to get the position with the clickedButton object.
If I'm not wrong VoidWalker, you are trying to get the position(index) of the item in the source array and not the actual position of the button on screen. If the former case is true read on, for the latter we have some good answers above.
What you need to do is to mark each button with an identifier that would be used to infer the position. A simple yet damn efficient approach.
At the time of creating the button:
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Add the current index to the name field of the Button
tiles25[count].Name = "Grid5-Btn" + count.ToString();
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
Then on button click you can simply do
void Tile_Click(Object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
var index = int(clickedButton.Name.Split("Grid5-Btn")[0]);
//...
}
This way you can add multiple pieces of informatio such as the hierarchy of grids on the page. You can exactly pinpoint which element to access without running any loops, which would be the case with Array.IndexOf

Show text when hovering over cell in TableLayoutPanel - C#

I've got a programatically created TableLayoutPanel, with each of its cells containing a Panel. Each Panel has a custom Label. (The Labels' Enabled property is set to false; not sure if that makes a difference.) I'd like to display the text of the Label whenever the user hovers over it with the mouse.
From what I've read, a ToolTip is a good way to do this, but I haven't been able to get it to work.
The TableLayoutPanel is name "tlp" for short and is a member of the form for easier access (likewise with the ToolTip, which is name "toolTip").
For now I'm just trying to get any kind of text. I'll replace my string here with the Label's text once I can get it to work.
private void hoverOverSpace(object sender, EventArgs e)
{
int row = tlp.GetRow((Panel)sender);
int col = tlp.GetColumn((Panel)sender);
toolTip.Show("Does this work?", tlp.GetControlFromPosition(col, row).Controls[0]);
//toolTip.Show("Does this work?", tlp.GetControlFromPosition(col, row));
}
Neither of my attempts to display the ToolTip have been successful. Am I doing something wrong/is there a better method for doing what I'm trying to accomplish?
EDIT: I've attempted to add the toolTip to each Panel but still nothing is happening
// Add Panels to TableLayoutPanel
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
// Create new Panel
Panel space = new Panel()
{
Size = new Size(45, 45),
Dock = DockStyle.Fill,
Margin = new Padding(0)
};
space.MouseClick += new MouseEventHandler(clickOnSpace);
CustomLabel info = new CustomLabel(false, 0, Color.White); // Create new CustomLabel
space.Controls.Add(info); // Add CustomLabel to Panel
tlp.Controls.Add(space, j, i); // Add Panel to TableLayoutPanel
toolTip = new ToolTip();
toolTip.SetToolTip(space, info.Text);
}
}
This answer is based on code presented in the answer to: tablelayoutPanel get cell location from mouse over, by: Aland Li Microsoft CSS.
#region GetPosition
// Modified from answer to: tablelayoutPanel get cell location from mouse over
// By: Aland Li Microsoft CSS
// https://social.msdn.microsoft.com/Forums/windows/en-US/9bb6f42e-046d-42a0-8c83-febb1dcf98a7/tablelayoutpanel-get-cell-location-from-mouse-over?forum=winforms
//The method to get the position of the cell under the mouse.
private TableLayoutPanelCellPosition GetCellPosition(TableLayoutPanel panel, Point p)
{
//Cell position
TableLayoutPanelCellPosition pos = new TableLayoutPanelCellPosition(0, 0);
//Panel size.
Size size = panel.Size;
//average cell size.
SizeF cellAutoSize = new SizeF(size.Width / panel.ColumnCount, size.Height / panel.RowCount);
//Get the cell row.
//y coordinate
float y = 0;
for (int i = 0; i < panel.RowCount; i++)
{
//Calculate the summary of the row heights.
SizeType type = panel.RowStyles[i].SizeType;
float height = panel.RowStyles[i].Height;
switch (type)
{
case SizeType.Absolute:
y += height;
break;
case SizeType.Percent:
y += height / 100 * size.Height;
break;
case SizeType.AutoSize:
y += cellAutoSize.Height;
break;
}
//Check the mouse position to decide if the cell is in current row.
if ((int)y > p.Y)
{
pos.Row = i;
break;
}
}
//Get the cell column.
//x coordinate
float x = 0;
for (int i = 0; i < panel.ColumnCount; i++)
{
//Calculate the summary of the row widths.
SizeType type = panel.ColumnStyles[i].SizeType;
float width = panel.ColumnStyles[i].Width;
switch (type)
{
case SizeType.Absolute:
x += width;
break;
case SizeType.Percent:
x += width / 100 * size.Width;
break;
case SizeType.AutoSize:
x += cellAutoSize.Width;
break;
}
//Check the mouse position to decide if the cell is in current column.
if ((int)x > p.X)
{
pos.Column = i;
break;
}
}
//return the mouse position.
return pos;
}
#endregion
It uses the TableLayoutPanelCellPosition computed by the referenced code to obtain the Control at that position (if any) and display its Text property as a ToolTip on the TableLayoutPanel.MouseHover event.
private void tableLayoutPanel1_MouseHover(object sender, EventArgs e)
{
Point pt = tableLayoutPanel1.PointToClient(Control.MousePosition);
TableLayoutPanelCellPosition pos = GetCellPosition(tableLayoutPanel1, pt);
Control c = tableLayoutPanel1.GetControlFromPosition(pos.Column, pos.Row);
if (c != null)
{
toolTip1.Show(c.Text, tableLayoutPanel1, pt, 500);
}
}
Edit:
I missed that the TLP is populated with controls with their Dock property set to DockStyle.Fill`. Such controls place placed in the TLP will receive the Mouse Events instead of the TLP. So as fix, add this method.
private void showtip(object sender, EventArgs e)
{
Point pt = tableLayoutPanel1.PointToClient(Control.MousePosition);
TableLayoutPanelCellPosition pos = GetCellPosition(tableLayoutPanel1, pt);
Control c = tableLayoutPanel1.GetControlFromPosition(pos.Column, pos.Row);
if (c != null && c.Controls.Count > 0)
{
toolTip1.Show(c.Controls[0].Text, tableLayoutPanel1, pt, 500);
}
}
Then wireup the each Panel and Label grouping like this:
this.panel4.MouseHover += new System.EventHandler(this.showtip);
this.label4.MouseHover += new System.EventHandler(this.showtip);

c# gomoku game label array

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.

Categories

Resources