draw picture box and get coordinates - c#

i'm newbie here and also in c#.
my project is to create a box in grid view.
then when click desired box, i'll get the box coordinate or position and box will change the colour.
when click another box, the previous box colour will change to original.
the box will resize when total size for rows x cols more than panel2 size.
i wanna extend the function of code by add new button NEXT, when click, then next picture box will be highlight and also coordinate will update. how to relate new button with existing picture box?
for (int cols = 0; cols < COLUMNS; cols++)
{
for (int rows = 0; rows < ROWS; rows++)
{
PictureBox newPic = new PictureBox();
newPic.Height = HEIGHT;
newPic.Width = WIDTH;
newPic.BackColor = Color.Maroon;
int x = cols * (HEIGHT + SPACE);
int y = rows * (WIDTH + SPACE);
newPic.Location = new Point(x + SPACE, y + SPACE);
newPic.Click += NewPic_Click;
items.Add(newPic);
this.panel2.Controls.Add(newPic);
}
}

Just for color switching, you only need the PictureBox which has been clicked on. It is stored in the sender parameter.
I you want the coordinates, you need to store some information on the PictureBox. You don't want to specify 50 handlers.
The way I would do is; to make use of the Tag property of a Control.
Your for-loop would be something like:
for (int cols = 0; cols < COLUMNS; cols++)
{
for (int rows = 0; rows < ROWS; rows++)
{
PictureBox newPic = new PictureBox();
newPic.Height = HEIGHT;
newPic.Width = WIDTH;
newPic.BackColor = Color.Maroon;
// instead of the coordinates, store the indices (for col, row)
newPic.Tag = new Point(cols, rows);
// I would use the Width on the cols, instead of the Height.
int x = cols * (WIDTH + SPACE);
int y = rows * (HEIGHT + SPACE);
newPic.Location = new Point(x + SPACE, y + SPACE);
newPic.Click += NewPic_Click;
items.Add(newPic);
this.panel2.Controls.Add(newPic);
}
}
And your handler would be something like:
// a field to store the previous selected picturebox.
private PictureBox _currentPictureBox = null;
private void NewPic_Click(object sender, EventArgs e)
{
// the picturebox which has been clicked, is stored in the sender object, but you need to cast it to a PictureBox.
PictureBox pb = (PictureBox)sender;
// just for the extra use of the Tag.
var location = (Point)pb.Tag;
// location.X Contains the Column index
// location.Y Contains the Row index
// did we have a previous picturebox?
if(_currentPictureBox != null)
{
// change the previous pictureBox back to Maroon
_currentPictureBox.BackColor = Color.Maroon;
}
// change the current to blue
pb.BackColor = Color.Blue;
// store the new one as the current. (so we can revert it)
_currentPictureBox = pb;
}
I haven't tested it, only in a notepad. So there might be some typo's. But I hope you get the idea.

Related

C# Labels do not move in the form

I wrote a C# code that makes labels from 0 to 100 in the form. when I run the project, only 0 appears in the form at the 15,156 location. actually, others have been created but they don't move in the x line. for example, 0 is on 15,156 and 1 should be in the 35,156 location and go ahead until the end.
for(int i = 0; i < 101; i++)
{
Label lbl = new Label();
lbl.Name = "lbl" + i.ToString();
lbl.Text = i.ToString();
lbl.Location = new Point(15 + j, 156);
this.Controls.Add(lbl);
j += 20;
}
Add lbl.AutoSize = true before adding lbl to Controls collection.
This way you're telling label to be wide enough pixels to let text be visible. If you omit this, label will be 100 pixels wide (default size) which will cause the first label (the one with Text 0) to hide second label, second label to hide third, third to hide fourth etc... Each label will be wide 100 pixels and text will only occupy first 10 pixels, hidden under previous label.
Also, increase that j variable a bit more (like 50 pixels) and add border to better see what's happening.
Define the size for your controls:
for(int i = 0; i < 101; i++)
{
Label lbl = new Label();
lbl.Name = "lbl" + i.ToString();
lbl.Text = i.ToString();
lbl.Location = new Point(15 + j, 156);
lbl.Size = new Size(65, 15);
this.Controls.Add(lbl);
j += 20;
}
Rererence:
https://support.microsoft.com/en-us/help/319266/how-to-programmatically-add-controls-to-windows-forms-at-run-time-by-u
One problem you're running into is that you're not specifying the size, so all the labels are being created with a width of 100, and the blank space of each label is covering up the text of the one below it. To resolve this, just set Size = new Size(width, height) when creating the control.
Also, when dynamically placing controls next to each other that are all the same size, we can just use the loop variable as a multiplier to determine the Left value for the Location. As long as we choose a Width that is wide enough to fit our longest value, this will work. This is good if you need a fixed column width for each number, but as the numbers get larger, there is less white space between them:
var labelSize = new Size(25, 20);
var padDistance = 2;
for (int i = 0; i < 101; i++)
{
Controls.Add(new Label
{
Name = $"lbl{i}",
Text = i.ToString(),
Size = labelSize,
Location = new Point(i * (padDistance + labelSize.Width), 156)
});
}
If, however, you would rather have all the numbers the same distance apart, then we can AutoSize the controls and use the Right value of the previous control to help determine the current control's Left value. In order to most easily keep track of the previous control, we can add the controls to a List<Label> and then get the previous one using an index in the list (note that we need to special-case the first label, since there is no control before it). In this case, there is equal white space between all the numbers, regardless of the number of characters they have:
var padDistance = 2;
var labels = new List<Label>();
for (int i = 0; i < 101; i++)
{
// For all but the first control, get the 'Right' value of the previous control
int left = (i == 0) ? padDistance : labels[i - 1].Right + padDistance;
labels.Add(new Label
{
Name = $"lbl{i}",
Text = i.ToString(),
AutoSize = true,
Location = new Point(left, 156)
});
Controls.Add(labels[i]);
}
if i understood you correctly try and feedback please i can help.
Vertical
int j = 5;
for (int i = 0; i < 101; i++)
{
Label lbl = new Label
{
Name = "lbl" + i.ToString(),
Text = "lbl " + i.ToString(),
Location = new Point(10, 10 + j),
Size = new Size(50, 20)
};
this.Controls.Add(lbl);
j += 20;
}
horizontal
int x = 50; int y = 20;
for (int i = 0; i < 101; i++)
{
Point location = new Point(x, y);
Label lbl = new Label
{
Name = "lbl" + i.ToString(),
Text = "lbl" + i.ToString(),
Location = location,
Width = 30
};
this.Controls.Add(lbl);
x += 40;
}

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);

Displaying pictureBox array

I would like to display 13 pictureBox, however, it ends up with only the last one visible.
So I was wondering if I did it in a wrong way.
The following code get image from resources folder.
var testP = new PictureBox();
for (int i = 0; i < 13; i++)
{
testP.Width = 65;
testP.Height = 80;
testP.BorderStyle = BorderStyle.None;
testP.SizeMode = PictureBoxSizeMode.StretchImage;
test[i] = getImage(testP, testPTemp[i]);
}
The following code is trying to display 13 pictureBox with shifting location.
These two codes segments should be able to perform the action.
test = new PictureBox[13];
for (var i = 0; i < 13; i++)
{
test[i].Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + testTemp[i]);
test[i].Left = 330;
test[i].Top = 500;
test[i].Location = new Point(test[i].Location.X + 0 * displayShift, test[i].Location.Y);
this.Controls.Add(test[i]);
}
Here is the getImage()
private PictureBox getImage(PictureBox pB, string i) // Get image based on the for loop number (i)
{
pB.Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + i); // Get the embedded image
pB.SizeMode = PictureBoxSizeMode.StretchImage;
return pB;
}
I'm pretty sure there are all PictureBox Controls but they have all the same location so they are lying above each other. That's why only the last one is visible to you.
I think you should replace the 0 with the i variable.
test[i].Location = new Point(test[i].Location.X + i * displayShift, test[i].Location.Y); this.Controls.Add(test[i]);
It's hard to tell the exact problem based off the code you've provided. One possible issue could be that when you are creating the PictureBoxes you only create a single instance before the for loop and then fill the array with references to that instance. Another possibility is that when you're calculating the X position of the controls, you're multiplying by 0 which will always result in 0 (meaning all the controls are at location 330).
Below is code that will achieve basically what you're trying but without all your code I can't give you a more specific example.
In Your Class
const int PICTURE_WIDTH = 65;
const int PICTURE_HEIGHT = 85;
Inside You Function
//Loop through each image
for(int i = 0; i < testTemp[i].length; i++)
{
//Create a picture box
PictureBox pictureBox = new PictureBox();
pictureBox.BorderStyle = BorderStyle.None;
pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
//Load the image date
pictureBox.Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + testTemp[i]);
//Set it's size
pictureBox.Size = new Size(PICTURE_WIDTH, PICTURE_HEIGHT);
//Position the picture at (330,500) with a left offset of how many images we've gone through so far
pictureBox.Location = new Point(330 + (i * PICTURE_WIDTH), 500);
//Add the picture box to the list of controls
this.Controls.Add(pictureBox);
}
If you need to keep a list of the picture boxes, just create a new list before the loop and add each pictureBox to the list inside the loop. If the control/window you're adding these PictureBoxes to needs to scroll left or right to see all the images set the AutoScroll property to true.

Why does my form need to be bigger than its containing panels to display them?

I'm using simple code to generate a grid (gridSize * gridSize fields with lines dividing them in column and row, basically a TicTacToe grid).
As I'm creating the panels dynamically during Form_Load, I need to also adjust the size of the form. However, setting it to gridSize * tileSize, gridSize * tileSize is not big enough - I found by experimentation that I need to add ~15 to width and ~40 to height for gridSize = 3 and tileSize = 120. Why is this?
Code below:
private void Form1_Load(object sender, EventArgs e)
{
const int tileSize = 120;
const int gridSize = 3;
/* Here: When setting size, I need to add 15 and 40? */
this.Size = new System.Drawing.Size(tileSize * gridSize + 15, tileSize * gridSize + 40);
// initialize the "board"
tictactoeFields = new Panel[gridSize, gridSize]; // column, row
// double for loop to handle all rows and columns
for (var n = 0; n < gridSize; n++)
{
for (var m = 0; m < gridSize; m++)
{
// create new Panel control which will be one
// tic tac toe field
var newPanel = new Panel
{
Size = new Size(tileSize, tileSize),
Location = new Point(tileSize * n, tileSize * m)
};
// add to our 2d array of panels for future use
tictactoeFields[n, m] = newPanel;
newPanel.BackColor = Color.White;
if(n != 0)
{
// Draw a line in front (to the left) of this panel
Panel leftSeparator = new Panel
{
Size = new Size(1, tileSize),
Location = newPanel.Location,
BackColor = Color.Black
};
Controls.Add(leftSeparator);
}
if(m != 0)
{
// Draw a line on top (above) this panel
Panel topSeparator = new Panel
{
Size = new Size(tileSize, 1),
Location = newPanel.Location,
BackColor = Color.Black
};
Controls.Add(topSeparator);
}
}
}
foreach(Panel pan in tictactoeFields)
{
// add to Form's Controls so that they show up
Controls.Add(pan);
}
}
The Size property is just a shorthand for setting the size of the Bounds property which includes nonclient elements such as scroll bars, borders, title bars, and menus.
What you should do is to set the size of the ClientRectangle property, or use the ClientSize shorthand.
There's also a DisplayRectangle property which includes padding, but in this case use the ClientRectangle property.
this.ClientSize = new Size((tileSize * gridSize), (tileSize * gridSize));

Performance issue when adding controls to a panel in C#

I am creating a panel and then adding some labels/buttons to it to form a grid. The issue is that if I add more than say 25x25 items to the panel, there is a terrible performance hit. I can resize the form ok but when I scroll the panel to see all the labels the program lags, the labels/buttons tear or flicker, and sometimes it can make the program unresponsive. I have tried adding the controls to a "DoubleBufferedPanel" that I created. This seems to have no effect. What else could I do? Sorry for such a large code listing. I didn't want to waste anyone's time.
namespace GridTest
{
public partial class Form1 : Form
{
private const int COUNT = 50;
private const int SIZE = 50;
private Button[,] buttons = new Button[COUNT, COUNT];
private GridPanel pnlGrid;
public Form1()
{
InitializeComponent();
pnlGrid = new GridPanel();
pnlGrid.AutoScroll = true;
pnlGrid.Dock = DockStyle.Fill;
pnlGrid.BackColor = Color.Black;
this.Controls.Add(pnlGrid);
}
private void Form1_Load(object sender, EventArgs e)
{
int x = 0;
int y = 0;
int offset = 1;
for (int i = 0; i < COUNT; i++)
{
for (int j = 0; j < COUNT; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Size = new Size(SIZE, SIZE);
buttons[i, j].Location = new Point(x, y);
buttons[i, j].BackColor = Color.White;
pnlGrid.Controls.Add(buttons[i, j]);
x = x + SIZE + offset;
}
x = 0;
y = y + SIZE + offset;
}
}
}
}
Also, the GridPanel class:
namespace GridTest
{
public class GridPanel : Panel
{
public GridPanel()
: base()
{
this.DoubleBuffered = true;
this.ResizeRedraw = false;
}
}
}
If you must add controls at run time, based on some dynamic or changing value, you might want to consider creating an image on the fly, and capturing mouse click events on its picturebox. This would be much quicker and only have one control to draw rather than hundreds. You would lose some button functionality such as the click animation and other automatic properties and events; but you could recreate most of those in the generation of the image.
This is a technique I use to offer users the ability to turn on and off individual devices among a pool of thousands, when the location in a 2-dimensional space matters. If the arrangement of the buttons is unimportant, you might be better offering a list of items in a listview or combobox, or as other answers suggest, a datagridview with button columns.
EDIT:
An example showing how to add a graphic with virtual buttons. Very basic implementation, but hopefully you will get the idea:
First, some initial variables as preferences:
int GraphicWidth = 300;
int GraphicHeight = 100;
int ButtonWidth = 60;
int ButtonHeight = 20;
Font ButtonFont = new Font("Arial", 10F);
Pen ButtonBorderColor = new Pen(Color.Black);
Brush ButtonTextColor = new SolidBrush(Color.Black);
Generating the image:
Bitmap ControlImage = new Bitmap(GraphicWidth, GraphicHeight);
using (Graphics g = Graphics.FromImage(ControlImage))
{
g.Clear(Color.White);
for (int x = 0; x < GraphicWidth; x += ButtonWidth)
for (int y = 0; y < GraphicHeight; y += ButtonHeight)
{
g.DrawRectangle(ButtonBorderColor, x, y, ButtonWidth, ButtonHeight);
string ButtonLabel = ((GraphicWidth / ButtonWidth) * (y / ButtonHeight) + x / ButtonWidth).ToString();
SizeF ButtonLabelSize = g.MeasureString(ButtonLabel, ButtonFont);
g.DrawString(ButtonLabel, ButtonFont, ButtonTextColor, x + (ButtonWidth/2) - (ButtonLabelSize.Width / 2), y + (ButtonHeight/2)-(ButtonLabelSize.Height / 2));
}
}
pictureBox1.Image = ControlImage;
And responding to the Click event of the pictureBox:
// Determine which "button" was clicked
MouseEventArgs em = (MouseEventArgs)e;
Point ClickLocation = new Point(em.X, em.Y);
int ButtonNumber = (GraphicWidth / ButtonWidth) * (ClickLocation.Y / ButtonHeight) + (ClickLocation.X / ButtonWidth);
MessageBox.Show(ButtonNumber.ToString());
I think you won't be able to prevent flickering having 625 buttons (btw, windows) on the panel. If such layout is mandatory for you, try to use the DataGridView, bind it to a fake data source containing the required number of columns and rows and create DataGridViewButtonColumns in it. This should work much more better then your current result ...

Categories

Resources