I have TableLayoutPanel that I programatically add Rows to. The User basically choses a Property and that is then displayed in the table along with some controls. I think I have a general understanding problem here and I will try to explain it.
One of the Controls in every row is a 'delete'-Button. That button should delete the row it is in. What I did is add an eventhandler to the button and set the current rowcount.
deleteTalent.Click += (sender, e) => buttonClickHandler(numberOfRows);
Code of the handler:
private void buttonClickHandler(int rowCount)
{
int count = rowCount - 1;
for (int i = count; i < (count + 5); i++)
{
balanceTable.Controls.RemoveAt(count);
}
balanceTable.RowStyles.RemoveAt(count);
balanceTable.RowCount--;
}
I looked at it for hours and played around. But I can't find a working clean solution. I'm also pretty new to C#
Here's the complete Function that creates a new row:
private void addBalanceItems(ToolStripMenuItem item)
{
int numberOfRows = balanceTable.RowCount;
if (numberOfRows > 1)
{
balanceTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize));
}
balanceTable.Height = numberOfRows * 45;
Steigerungsrechner rechner = new Steigerungsrechner();
string tag = item.Tag.ToString();
//change that asap :(
if (tag == "A") { rechner.column = 1; }
if (tag == "B") { rechner.column = 2; }
if (tag == "C") { rechner.column = 3; }
if (tag == "D") { rechner.column = 4; }
if (tag == "E") { rechner.column = 5; }
if (tag == "F") { rechner.column = 6; }
if (tag == "G") { rechner.column = 7; }
if (tag == "H") { rechner.column = 8; }
Label talentName = new Label();
talentName.Text = item.Text;
talentName.Height = standardHeight;
talentName.TextAlign = ContentAlignment.MiddleLeft;
talentName.AutoSize = true;
Label cost = new Label();
cost.TextChanged += (sender, e) => costChangeHandler(cost);
cost.Height = standardHeight;
cost.TextAlign = ContentAlignment.MiddleLeft;
TextBox startValue = new TextBox();
startValue.TextChanged += (sender, e) => startValueChangeHandler(rechner, startValue, cost);
startValue.Height = standardHeight;
startValue.TextAlign = HorizontalAlignment.Center;
TextBox endValue = new TextBox();
endValue.TextChanged += (sender, e) => endValueChangeHandler(rechner, endValue, cost);
endValue.Height = standardHeight;
endValue.TextAlign = HorizontalAlignment.Center;
Button deleteTalent = new Button();
deleteTalent.Text = "x";
deleteTalent.Click += (sender, e) => buttonClickHandler(numberOfRows);
deleteTalent.Height = standardHeight;
balanceTable.Controls.Add(talentName);
balanceTable.Controls.Add(startValue);
balanceTable.Controls.Add(endValue);
balanceTable.Controls.Add(cost);
balanceTable.Controls.Add(deleteTalent);
balanceTable.Visible = true;
balanceTable.RowCount++;
}
Any help would be greatly appreciated! :)
Yeah, removing an arbitrary row from a TableLayoutPanel is not at all intuitive. They really screwed up the design on this one.
The only way to remove rows is by setting the RowCount property. This alone is strange enough; that property sure seems like it should be read-only and code that does this looks wrong to me every time I see it.
But beyond that, the consequence of this design is that you cannot remove rows from the middle. Resetting the RowCount property will just cause rows to be lopped off of the bottom.
The workaround is a bit unwieldy, with multiple steps to get wrong:
Remove the controls from the row you want to delete
If applicable, move those controls to to another row.
Move all of the controls in the other rows that come after the row you wish to delete up a row.
Finally, remove the last row by decrementing the value of the RowCount property.
A quick Google search reveals that someone has written and shared code purporting to do this. It's in VB.NET, but that should be easily translated into your native dialect.
I'll admit that I've been known to just punt and set the RowHeight of the row I wish to "remove" to 0. This way, autosizing does the work for you. You probably still want to remove the controls it contains, though.
Here is a static class that can help you remove any row by it's index:
using System.Windows.Forms;
public static class TableLayoutHelper
{
public static void RemoveArbitraryRow(TableLayoutPanel panel, int rowIndex)
{
if (rowIndex >= panel.RowCount)
{
return;
}
// delete all controls of row that we want to delete
for (int i = 0; i < panel.ColumnCount; i++)
{
var control = panel.GetControlFromPosition(i, rowIndex);
panel.Controls.Remove(control);
}
// move up row controls that comes after row we want to remove
for (int i = rowIndex + 1; i < panel.RowCount; i++)
{
for (int j = 0; j < panel.ColumnCount; j++)
{
var control = panel.GetControlFromPosition(j, i);
if (control != null)
{
panel.SetRow(control, i - 1);
}
}
}
var removeStyle = panel.RowCount - 1;
if (panel.RowStyles.Count > removeStyle)
panel.RowStyles.RemoveAt(removeStyle);
panel.RowCount--;
}
}
One thing to mention: controls that we get via panel.GetControlFromPosition(...) must be visible or it will return null instead of invisible controls.
Remove existing controls of rowCount at first
for(int i = 0; i < panel.ColumnCount; i++){
Control Control = panel.GetControlFromPosition(i, rowCount);
panel.Controls.Remove(Control);
}
Then remove row
panel.RowStyles.RemoveAt(rowCount-1);
Removing complete Table -
tableLayoutPanel1.Controls.Clear();
tableLayoutPanel1.RowStyles.Clear();
Set your Headline of the Table again -
tableLayoutPanel.RowCount = 1;
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
tableLayoutPanel.Controls.Add(new Label() { Text = "MONTH", Font = new Font("Century Gothic", 12, FontStyle.Bold), ForeColor = Color.LightGray }, 0, tableLayoutPanel.RowCount - 1);
tableLayoutPanel.Controls.Add(new Label() { Text = "YEAR", Font = new Font("Century Gothic", 12, FontStyle.Bold), ForeColor = Color.LightGray }, 1, tableLayoutPanel.RowCount - 1);
tableLayoutPanel.Controls.Add(new Label() { Text = "MEASURED WAFERS", Font = new Font("Century Gothic", 12, FontStyle.Bold), ForeColor = Color.LightGray }, 2, tableLayoutPanel.RowCount - 1);
3 Columns - 1 Row
Maybe someone can use my codesnipped, works proper good...
You cannot completely delete a row on tablelayoutpanel but there is a workaround:
Remove all the controls in the row, easier if you know the names of the controls cause you can call the dispose method.
Set the height of the row to maybe 2px using the row style method
(e.g. tablelayoutpanel1.Rowstyle(index).height=2)
For me this worked wonders the, row was completely collapsed the row regardless of the row index.
Related
I am working on a Tic Tac Toe simulator for a class and have run into an issue.
I created a 2-dimensional array to simulate the board and populate it with either 0 or 1 in all the boxes.
The issue I am having is getting those numbers to apply to the labels I have created (a1, a2, a3, b1, b2, etcetera).
Is there a way that my nested for loops can have each element in the array apply to a new label? I can't seem to find anything in my book or online about this.
Here is my related code:
private void newBTN_Click(object sender, EventArgs e)
{
Random rand = new Random();
const int ROWS = 3;
const int COLS = 3;
int [,] board = new int[ROWS, COLS];
for (int row = 0; row < ROWS; row++)
{
for (int col = 0; col < COLS; col++)
{
board[row, col] = rand.Next(1);
}
}
}
What are the names of the labels? I assumed below that the labels are Label0_0, Label0_1, Label1_1 and so on... This way you can find them using the row and column values.
You want to find the Label control on your form dynamically, because you don't know the name in advance while coding.
If you know the name in advance you just say: label1.Text = "1";.
But in your case, you are trying to find a particular control in each iteration of the loop. So you need to have a name for the labels so you can find them using Form.Controls.Find(string, bool) like this:
var row = 4;
var col = 6;
var l = this.Controls.Find("Label" + row.ToString() + "_" + col.ToString(), false).FirstOrDefault() as Label;
if (l == null)
{
//problem... create label?
l = new Label() { Text = "X or O" }; //the position of this need to be set (the default is 0,0)
this.Controls.Add(l);
}
else
{
l.Text = "X or O";
}
Your board stores integers, which is an internal representation of your game state. You can create a UniformGrid that holds Label for your game GUI. The code below returns a grid based on your current board. You need to add this returned grid to your MainWindow (or whatever you use) to see it.
private UniformGrid fillLabels(int[,] board)
{
int numRow = board.GetLength(0);
int numCol = board.GetLength(1);
UniformGrid g = new UniformGrid() { Rows = numRow, Columns = numCol };
for (int i = 0; i < numRow; i++)
{
for (int j = 0; j < numCol; j++)
{
Label l = new Label();
l.Content = (board[i, j] == 0) ? "O" : "X";
Grid.SetRow(l, i);
Grid.SetColumn(l, j);
g.Children.Add(l);
}
}
return g;
}
First, do not re-create (and re-initialize) Random each time you need it: it makes generated sequences skewed badly:
private static Random s_Rand = new Random();
Try not implement algorithm in the button enent directly, it's a bad practice:
private void CreateField() { ... }
private void newBTN_Click(object sender, EventArgs e) {
CreateField();
}
putting all together:
private static Random s_Rand = new Random();
private void ApplyLabelText(String name, String text, Control parent = null) {
if (null == parent)
parent = this;
Label lb = parent as Label;
if ((lb != null) && (String.Equals(name, lb.Name))) {
lb.Text = text;
return;
}
foreach(Control ctrl in parent.Controls)
ApplyLabelText(name, text, ctrl);
}
private void CreateField() {
for (Char row = 'a'; row <= 'c'; ++row)
for (int col = 1; col <= 3; ++col)
ApplyLabelText(row.ToString() + col.ToString(), s_Rand.Next(1) == 0 ? "O" : "X");
}
private void newBTN_Click(object sender, EventArgs e) {
CreateField();
}
How about you skip the INTEGER board and go directly to a Label array?
You can then do the following to loop trough all of them:
Label[,] listOfLabels; // Do also initialize this.
foreach(Label current in listOfLabels)
{
current.Text = _rand.Next(2) == 0 ? "0" : "X";
}
When I run the following code, it populates the label1 control one time. Then the label1 control does nothing else. How do I get the label1 control to change on the mouse enter events. Please provide code examples.
int currentXposition, currentYposition;
const string positionLabel = "Current Position: ";
private void Test_Load(object sender, EventArgs a)
{
var temp=Color.Transparent; //Used to store the old color name of the panels before mouse events
var colorName = Color.Red; //Color used to highlight panel when mouse over
int numBlocks = 8; //Used to hold the number of blocks per row
int blockSize=70;
//Initialize new array of Panels new
string[,] Position = new string[8, 8];
Panel[,] chessBoardPanels = new Panel[numBlocks, numBlocks];
string Alphabet = "A,B,C,D,E,F,G,H";
string Numbers ="1,2,3,4,5,6,7,8";
string[] alphaStrings = Alphabet.Split(',');
string[] numStrings=Numbers.Split(',');
// b = sub[0];
int FirstValue, SecondValue;
//Store Position Values
for (int firstValue = 0; firstValue < 8; ++firstValue)
{
FirstValue = Alphabet[firstValue];
for (int SecValue = 0; SecValue < 8; ++SecValue)
{
SecondValue = Numbers[SecValue];
Position[firstValue, SecValue] = alphaStrings[firstValue] + numStrings[SecValue];
}
}
//Loop to create panels
for (int iRow = 0; iRow < numBlocks; iRow++)
{
for (int iColumn = 0; iColumn < numBlocks; iColumn++)
{
Panel p = new Panel();
//set size
p.Size = new Size(blockSize, blockSize);
//set back colour
p.BackColor = (iRow + (iColumn % 2)) % 2 == 0 ? Color.Black : Color.White;
//set location
p.Location = new Point(blockSize *iRow+15, blockSize * iColumn+15);
chessBoardPanels[iRow, iColumn] = p;
chessBoardPanels[iRow,iColumn].MouseEnter += (s,e) =>
{
currentXposition = iRow;
currentYposition = iColumn;
var oldColor = (s as Panel).BackColor;
(s as Panel).BackColor = colorName;
temp = oldColor;
label1.Text = Position[iRow, iColumn];
};
chessBoardPanels[iRow, iColumn].MouseLeave += (s, e) => {
(s as Panel).BackColor = temp;
};
groupBox1.Controls.Add(p);
}
}
}
I'm answering this only because I think it's important to show what happens when scope is confused. The reason you're having your issue is the scope of your variables. Your label is changing iRow * iColumn times, but only during initial execution. From then on, iRow and iColumn are fixed at their final values.
To achieve your desired end goal, it'd be easiest to create an extension of Panel:
public class ChessPanel : Panel {
private const Color HighlightColor = Color.Red;
public int iColumn { get; set; }
public int iRow { get; set; }
public Color PrimaryColor { get; set; }
public ChessPanel() : base()
{
this.MouseEnter += (s,e) =>
{
this.PrimaryColor = this.BackColor;
this.BackColor = HighlightColor;
};
this.MouseLeave += (s,e) =>
{
this.BackColor = this.PrimaryColor;
};
}
}
This will then allow you to reduce your code as follows:
int currentXposition, currentYposition;
const string positionLabel = "Current Position: ";
private void Test_Load(object sender, EventArgs a)
{
var temp=Color.Transparent; //Used to store the old color name of the panels before mouse events
var colorName = Color.Red; //Color used to highlight panel when mouse over
int numBlocks = 8; //Used to hold the number of blocks per row
int blockSize=70;
//Initialize new array of Panels new
string[,] Position = new string[8, 8];
ChessPanel[,] chessBoardPanels = new ChessPanel[numBlocks, numBlocks];
string Alphabet = "A,B,C,D,E,F,G,H";
string Numbers ="1,2,3,4,5,6,7,8";
string[] alphaStrings = Alphabet.Split(',');
string[] numStrings=Numbers.Split(',');
int FirstValue, SecondValue;
//Store Position Values --- no idea what this is supposed to do...
for (int firstValue = 0; firstValue < 8; ++firstValue)
{
FirstValue = Alphabet[firstValue];
for (int SecValue = 0; SecValue < 8; ++SecValue)
{
SecondValue = Numbers[SecValue];
Position[firstValue, SecValue] = alphaStrings[firstValue] + numStrings[SecValue];
}
}
//Loop to create panels
for (int iRow = 0; iRow < numBlocks; iRow++)
{
for (int iColumn = 0; iColumn < numBlocks; iColumn++)
{
ChessPanel p = new ChessPanel();
//set size
p.Size = new Size(blockSize, blockSize);
//set back colour
p.BackColor = (iRow + (iColumn % 2)) % 2 == 0 ? Color.Black : Color.White;
//set location
p.Location = new Point(blockSize *iRow+15, blockSize * iColumn+15);
p.MouseEnter += (s,e) =>
{
var cpSelf = s as ChessPanel;
if (cpSelf != null)
{
label1.Text = Position[cpSelf.iRow, cpSelf.iColumn];
}
};
groupBox1.Controls.Add(p);
chessBoardPanels[iRow, iColumn] = p;
}
}
}
I'm assuming you're making use of many of those variables later on int the program, and looking at this kind of makes my head hurt, so I left most of it in place.
Delegates are a very powerful and useful utility, but they can cause confusion with variable scope. Be very careful when using these, and make sure you treat them as functional units that execute later and can therefore only rely on the state of the program at execution of the block rather than at creation of the block. If you notice, I kept the reference to the Position array simply to show that you can still access local variables within the scope of the delegate because it is technically still in scope. Structurally, this could easily be moved into the ChessPanel class and referenced 100% locally. This example should be used as a caution as it can show how a "local" variable that many people assume are garbage-collected at end of function execution can hang around and eat up memory.
This code is untested and may have minor syntax errors. Hopefully the spirit of the structure is understood.
My question is: When I click some Checkbox, how can I get the current checkbox control's index from DataGridView
Here is my snick code
dataGridView2.RowCount = 5;
dataGridView2.ColumnCount = 4;
for (int i = 0; i < dataGridView2.ColumnCount; i++)
{
for (int j = 0; j < dataGridView2.RowCount; j++)
{
box = new CheckBox();
box.Text = "MyDate";
//box.Size = new System.Drawing.Size(15, 15);
dataGridView2.Controls.Add(box);
Rectangle rec = dataGridView2.GetCellDisplayRectangle(i, j, true);
box.Left = rec.Left;
box.Top = rec.Top;
}
}
}
It looks like that you try adding pure CheckBoxes to your DataGridView without using a DataGridViewCheckBoxColumn, the solution for this approach is simple like this:
for (int i = 0; i < dataGridView2.ColumnCount; i++)
{
for (int j = 0; j < dataGridView2.RowCount; j++)
{
box = new CheckBox();
box.Text = "MyDate";
//box.Size = new System.Drawing.Size(15, 15);
dataGridView2.Controls.Add(box);
Rectangle rec = dataGridView2.GetCellDisplayRectangle(i, j, true);
box.Left = rec.Left;
box.Top = rec.Top;
//Added code
box.Tag = new Point(i,j);
box.Click += CheckBoxesClicked;
}
}
private void CheckBoxesClicked(object sender, EventArgs e){
CheckBox chb = sender as CheckBox;
if(chb.Tag != null) {
Point coord = (Point)chb.Tag;
MessageBox.Show(string.Format("Row index: {0}\nColumn index: {1}", coord.Y, coord.X);
}
}
You should use a DataGridViewCheckBoxColumn instead, with that approach, you can handle the event CellContentClick...
if you are using the CellContentClick event or any other event that you get the DataGridViewCellEventArgs then you have ColumnIndex and RowIndex properties that are the column and row of the cell changed
Check this links. This gives you details about grid view.
http://msdn.microsoft.com/en-us/library/ms972814.aspx
http://msdn.microsoft.com/en-us/library/aa479344.aspx
I'm doing an exercise on asp.net using code generated tables with a very simple code:
protected void btnAceptar_Click(object sender, EventArgs e)
{
tblGenerar.Controls.Clear();
for(int i = 0; i < Convert.ToInt32(txtRows.Text);i++)
{
TableRow rowNew = new TableRow();
tblGenerar.Rows.Add(rowNew);
for (int j = 0; j < Convert.ToInt32(txtCols.Text);j++ )
{
TableCell cellNew = new TableCell();
rowNew.Cells.Add(cellNew);
cellNew.Text = txtTexto.Text;
if (chkMargen.Checked == true)
{
cellNew.BorderStyle = BorderStyle.Inset;
cellNew.BorderWidth = 1;
}
}
}
}
The first time I choose to create border on the table, it works, but next time I choose to generate the table without the borders, the borders from last generated table are still there. Additional cells appears with no borders.
Why does this happen if I'm using Controls.Clear() and how can I solve it?
Thanks.
Put else condition within your code.
else
{
cellNew.BorderStyle = BorderStyle.None;
cellNew.BorderWidth = 0;
}
or you can do something like following.
cellNew.BorderStyle = BorderStyle.None;
if (chkMargen.Checked == true)
{
cellNew.BorderStyle = BorderStyle.Inset;
cellNew.BorderWidth = 1;
}
And you are done.
This is because once your Table is generated you can not apply changes on them and to do that you need to explicitly remove border first and then apply if check box is checked.
i've 6 label controls in a form: label1, label2...label6.
How to 'refer' to the control in a loop like this:
for (i=1;i<=6;i++) {
label[i].text = ...;
}
Thank you
Try,
Label []labels={Label1,Label2,Label3};
Here's another way:
for (int n = 1; n < 4; n++)
{
Control[] Temp = Controls.Find("Label" + n, false);
Temp[0].Text = n.ToString();
}
Let's assume this is WinForms and that your "labels" are controls - the Form has a Controls property, which is a collection of controls associated with that container, so, we ought to be able to use Linq to query this, get the controls of the type we want, then iterate them, as such:
using System.Linq;
var labels = from control in Controls where control is Label select control;
for (i = 1; i <= controls.Count; i++)
{
labels[i].text = i.ToString();
}
A little rough, but you aren't very specific - it should be a decent starting point if nothing else.
EDIT:
OK, I thought I'd take the time to look into it, and Form.Controls doesn't like being used in Linq (in that straightforward way, at least), so as an alternative, this should help:
private List<Label> GetLabels()
{
var result = new List<Label>();
foreach (var control in Controls)
{
if (control is Label)
{
result.Add(control as Label);
}
}
return result;
}
The above method could even be factored in a genericised way rather simply; but then you can proceed:
var labels = GetLabels();
for (int i = 0; i <= labels.Count; i++)
{
labels[i].Text = i.ToString();
}
You can implement something like this:-
int y = 0;
int index = 0;
Label[] labels = new Label[6];
foreach (Student std in StudentList)
{
labels[index] = new Label();
labels[index].Text = std.Name;
labels[index].ForeColor = Color.Red;
labels[index].Location = new Point(0, y);
labels[index].Size = new Size(50, 12);
y = y + 10;
++index;
}
// Add the Label control to the form.
mPanel.Controls.AddRange(labels);