Before I generate buttons in my program, it's supposed to clear them using:
for (int i = 0; i < buttons.Length; i++)
this.Controls.Remove(buttons[i]);
However all the buttons from the previous generation remain. What might be causing this?
(Below is the entire function, numButton changes in other functions.)
int numButtons = 5;
Button[] buttons = new Button[10];
private void generate_buttons()
{
for (int i = 0; i < buttons.Length; i++)
{
this.Controls.Remove(buttons[i]);
}
for (int i = 0; i < numButtons; i++)
{
buttons[i] = new Button();
buttons[i].Name = "btn" + i.ToString();
buttons[i].Text = Convert.ToString(i + 1);
buttons[i].Size = new Size(40, 24);
int yOffset = 40;
int xOffset = 20 + i * 42;
buttons[i].Location = new Point(xOffset, yOffset);
buttons[i].BackColor = System.Drawing.SystemColors.Control;
buttons[i].Enabled = false;
buttons[i].Click += new EventHandler(this.clickMe);
buttons[i].Visible = true;
this.Height = yOffset + 104;
this.Width = xOffset + 75;
}
Controls.AddRange(buttons);
}
Although you're removing the buttons from the control collection, you're not removing it from your buttons array. Add
Array.Clear(buttons, 0, buttons.Length);
I've also modified the removal loop to explicitly dispose of any resources held by the button as shown here on MSDN.
for (int i = 0; i < buttons.Length; i++)
{
//you can change break to 'continue' if you remove buttons
//from the array randomly so that if it encounters a null
//it will carry on reading the rest of the array.
if (buttons[i] == null)
break;
//dispose any button resources and event handlers so no references remain
buttons[i].Click -= new EventHandler(this.clickMe);
this.Controls.Remove(buttons[i]);
buttons[i].Dispose();
}
Array.Clear(buttons, 0, buttons.Length);
//..
Look into using a generic list collection instead. This allows you to use the .Add() and .Remove()/.RemoveAt() methods to more easily add and remove elements.
Tutorial/examples: http://www.dotnetperls.com/list
Related
I have a problem adding picture boxes using Controls class on another picture box that already exists.
when i load the form i don't see any changes because the "coded" pictures are under the main picture (Arena)
Here is my code:
void drawSpikes()
{
PictureBox[] spikes = new PictureBox[Arena.Height / 25 * 2];
int position = 0;
byte wall = 1;
byte spike_count = 0;
for (int i = 0; i < Arena.Height / 25 * 2; i++)
{
spikes[i] = new PictureBox();
}
foreach (var Spike in spikes)
{
if (spike_count == 18) wall = 2;
Spike.Size = new Size(25, 25);
if (wall == 1)
{
Spike.Location = new Point(21, position);
Spike.BackColor = Color.Yellow;
}
if (wall == 2)
{
Spike.Location = new Point(position, 250);
Spike.BackColor = Color.Yellow;
}
if (position == 450) position = 0;
position += 25;
spike_count += 1;
Controls.Add(Spike);
}
}
How can i fix it ?
sorry for the function disconnection (i am new in stack overflow).
You can use the BringToFront() function after adding the picture box to the controls collection:
Controls.Add(Spike);
Spike.BringToFront();
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
Here is the code. Help will be much obliged. I want the panels to be added to tabePage1, but it is being added to the form instead.
private void tabPage1_Click(object sender, EventArgs e)
{
int i, j;
for (i = 1; i <= 3; i++)
{
for (j = 1; j <= 4; j++)
{
Panel a = new Panel();
a.Location = new Point(i * 200, j * 50);
a.Width = 180;
a.Height = 40;
a.Name = "Rom " + (((i * 4) - 3) + (j - 1));
a.BackColor = Color.Yellow;
a.AllowDrop = true;
a.DragDrop += new System.Windows.Forms.DragEventHandler(this.panel1_DragDrop);
a.DragOver += new System.Windows.Forms.DragEventHandler(this.panel1_DragOver);
a.Visible = true;
Label l = new Label();
l.Location = new Point(10, 10);
l.Width = 180;
l.Text = a.Name;
a.Controls.Add(l);
l.AllowDrop = true;
this.Controls.Add(a);
This code:
this.Controls.Add(a);
adds the control to the Form, because your tabPage1_Click method is in a subclass of Form (making this refer to the Form). To add the panels to tabPage1, do this instead:
tabPage1.Controls.Add(a);
Incidentally, do you really want to add all the panels to a single TabPage, or do you want to create a TabPage for each panel? If the latter, the code will obviously look different.
Edit: in answer to your comment, you can add to a different TabPage by referring to it by name as above (e.g. tabPage2.Controls.Add(a);) or, if you want to add a set of panels to each TabPage in your TabControl, you could do something like this:
foreach (TabPage tp in yourTabControl.TabPages)
{
// create panel...
tp.Controls.Add(a);
// ...
}
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 ...
my panel in my windows form application doesn't include all the buttons i asked it to. It shows only 1 button, Here is the code
private void AddAlphaButtons()
{
char alphaStart = Char.Parse("A");
char alphaEnd = Char.Parse("Z");
for (char i = alphaStart; i <= alphaEnd; i++)
{
string anchorLetter = i.ToString();
Button Buttonx = new Button();
Buttonx.Name = "button " + anchorLetter;
Buttonx.Text = anchorLetter;
Buttonx.BackColor = Color.DarkSlateBlue;
Buttonx.ForeColor = Color.GreenYellow;
Buttonx.Width = 30;
Buttonx.Height = 30;
this.panelButtons.Controls.Add(Buttonx);
//Buttonx.Click += new System.EventHandler(this.MyButton_Click);
}
}
Aren't they all going to be on the same position?
Try setting Buttonx.Location = new Point(100, 200);
(but with different points for different buttons)
You could use a FlowLayoutPanel, which would take care of the layout for you, or you need to track the locations yourself, or which could look something like this:
private void AddAlphaButtons()
{
char alphaStart = Char.Parse("A");
char alphaEnd = Char.Parse("Z");
int x = 0; // used for location info
int y = 0; // used for location info
for (char i = alphaStart; i <= alphaEnd; i++)
{
string anchorLetter = i.ToString();
Button Buttonx = new Button();
Buttonx.Name = "button " + anchorLetter;
Buttonx.Text = anchorLetter;
Buttonx.BackColor = Color.DarkSlateBlue;
Buttonx.ForeColor = Color.GreenYellow;
Buttonx.Width = 30;
Buttonx.Height = 30;
// set button location
Buttonx.Location = new Point(x, y);
x+=30;
if(x > panel1.Width - 30)
{
x = 30;
y+=30;
}
this.panelButtons.Controls.Add(Buttonx);
//Buttonx.Click += new System.EventHandler(this.MyButton_Click);
}
}