Tricky : Array of controls dynamically created causing problems - c#

I put "Tricky" in the title because I'm aware that it will be hard to understand precisely what I want but I'll try to be clear.
I have a textBox with an event for TextChange which allow me to create the number of texboxes the user want (we will call it tChange).
Here is a part of the code for this event :
int tester;
bool flag = false;
if (!Int32.TryParse(tChange.Text, out tester))
{
flag = false;
return;
}
else
{
num = Convert.ToInt16(tChange.Text);
flag = true;
}
if (flag == true)
{
if (num >= 1)
{
for (int i = 0; i < num; i++)
{
this.Size = new Size(590, 225 + 105 * i);
textBoxesQ[i] = new TextBox();
this.Controls.Add(textBoxesQ[i]);
textBoxesQ[i].Size = new Size(45, 20);
textBoxesQ[i].Location = new Point(25, 100 + 100 * i - 1);
}
}
}
So the user enter the value and everything is OK. If he wants to change the number in the tChange, no problem too! The form is resize and the TextBoxes are created. However if he does this (change the value of tChange), everything goes wrong! Errors like
Index out of range
or I can't get the values from the TextBoxes etc..
I started thinking that the TexBoxes were created in front of the previous ones and the error came from this, so I tried to put the new ones to front, bring the old ones to front but none worked..
textBoxesQ[i].BringToFront();
textBoxesQ[i].SendToBack();
I also tried to delete the old ones before creating the new but I think that my code was wacky and it didn't work at all.
textBoxesQ[i].Dispose();
EDIT : As #Dr. Stitch said, It may come from not reinitializing the TextBoxes each time the text in tChange is changed. Now I just need to figure out how to make it happen.

You need to do three things when the number changes:
Remove any existing textboxes
Create a new array with the new size, and save a reference to that new array into your field (textBoxesQ)
Initialize the array
So something like:
if (textBoxesQ != null)
{
foreach (var textBox in textBoxesQ)
{
Controls.Remove(textBox);
}
}
textBoxesQ = new TextBox[size];
for (int i = 0; i < size; i++)
{
var textBox = new TextBox
{
Size = new Size(45, 20);
Location = new Point(25, 100 + 100 * i - 1);
};
Controls.Add(textBox);
textBoxesQ[i] = textBox;
}

Related

C# Dynamicaaly Add and Delete buttons

From the below code, I dynamically create a list of buttons based on client names provided by a TCP connection from the clientNames[i] list.
private void updateClientListUI()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.updateClientListUI));
}
else
{
//Debug.WriteLine(clientNames[0]);
int basex = subPanelClient.Location.X;
int basey = subPanelClient.Location.Y;
for (int i = 0; i < clientNames.Count; i++)
{
Button b = new Button();
b.Left = basex;
b.Top = basey;
b.Size = new Size(25, 25); // <== add this line
b.Dock = DockStyle.Top;
b.ForeColor= Color.Gainsboro;
b.FlatStyle= FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
b.Padding= new Padding(35, 0, 0, 0);
b.TextAlign = ContentAlignment.MiddleLeft;
basey += 25;
b.Name = clientNames[i];
b.Text = clientNames[i];
subPanelClient.Controls.Add(b);
buttonsAdded.Insert(i, b);
}
}
}
What I am trying to figure out, is how to delete a button (i). What I attempted is the following:
private void removingButtons(int i)
{
if (buttonsAdded.Count > 0)
{
Button buttonToRemove = buttonsAdded[i];
subPanelClient.Controls.Remove(buttonToRemove);
buttonsAdded.Remove(buttonToRemove);
}
}
Or if anyone would know how to update the current list of buttons from updateClientListUI() to the current latest list from clientNames[i] instead of a list being added onto another list.
The issue that Im getting is that obviously every time there is a connection or disconnection the list just keeps getting added instead of refreshing to the current list.
One way to do it would be to clear the old buttons on each update.
foreach (Button b in buttonsAdded)
{
subPanelClient.Controls.Add(b);
}
buttonsAdded.Clear();
for (int i = 0; i < clientNames.Count; i++)
{
Button b = new Button();
...
...
If the subPanelClient only contains those buttons, you could just use
subPanelClient.Controls.Clear();
Last remark: Your removingButtonk function seems to use this.Controls.Remove instead of subPanelClient.Controls.Remove.

dynamically created labels overlap each other

I want to create labels that will follow one another. I have a grid name WordTemplateLayout to which I want to add the labels. I add them dynamically on the wpf window constructor after InitializeComponent() is called. Here is the method creating the labels:
private void CreateWordTemplate()
{
IList<char> template = CurrentGame.Template;
double widthPerBox = WordTemplateLayout.Width / template.Count;
//template.Count being a number, irrelevant to the question
for (int i = 0; i < template.Count; i++)
{
var templateVisual = new Label();
templateVisual.Name = "c" + i;
templateVisual.Width = widthPerBox;
templateVisual.Height = WordTemplateLayout.Height;
templateVisual.Background = new SolidColorBrush(Colors.Aqua);
WordTemplateLayout.Children.Add(templateVisual);
}
}
the problem being, that what actually appends is that instead of the labels lining up one after the other, they overlap each other:
The aqua box is all the labels overlap each other
so what I am asking, is how can I make the labels line up (horizontally) instead of to overlap?
As others have pointed out, you're better off using a StackPanel, or learning how to use viewmodels and data binding. Having said that, to answer your direct question, here's how you'd do it programmatically.
**NOTE: Ignore the 5 that I'm passing in to the methods, and instead use your template.Count. This is was just for me to get it to work on my end.
public MainWindow()
{
InitializeComponent();
CreateGridLayout(5);
CreateWordTemplate(5);
}
// Define the Grid layout
// If you want the labels to follow one another horizontally, define columns.
// If you want them stacked vertically, instead define rows.
private void CreateGridLayout(int count)
{
for (int i = 0; i < count; i++)
{
WordTemplateLayout.ColumnDefinitions.Add(new ColumnDefinition());
}
}
private void CreateWordTemplate(int count)
{
double widthPerBox = WordTemplateLayout.Width / 5;
for (int i = 0; i < 5; i++)
{
var templateVisual = new Label();
templateVisual.SetValue(Grid.ColumnProperty, i); // SET YOUR COLUMN HERE!
templateVisual.Name = "c" + i;
templateVisual.Width = widthPerBox;
templateVisual.Height = WordTemplateLayout.Height;
templateVisual.Background = new SolidColorBrush(Colors.Aqua);
templateVisual.Content = templateVisual.Name;
WordTemplateLayout.Children.Add(templateVisual);
}
}
you need to use another layout so the new elements will get in normal order. try stackPanel.
you can use grid and give each new label Row=index.
you can give each new label margin, like newLabel.margin-top = index*50

Read mutliple data from table and create textboxes in form

My table in MySQL is like this:
I have a panel in my Form, and I want to create a TextBox for each data on this table and write sikicerik data to that TextBox's text.
Actually I did it but it creates only one TextBox and selects only the first data on the table.
My code is like this:
int count = oku.FieldCount;
reader.Read();
{
for (int i = 0; i < count; i++)
{
TextBox txt1 = new TextBox();
Point txtyer = new Point(x, y);
txt1.Text = reader["sikicerik"].ToString();
txt1.Name = i.ToString();
x = x + 25;
y = y + 25;
panel1.Controls.Add(txt1);
}
}
It creates just one TextBox and writes "5" in it.
How can I do that repeatedly?
What you need to understand is that you need to call reader.Read() after going through each record. Let's say your result data set has 5 records, so in order to read the 1st record, you need to call reader.Read() which would populate the reader object with the appropriate data. In order to read the 2nd record, you need to again call reader.Read(). Something like this:
int count = reader.FieldCount;
for (int i = 0; i < count; i++)
{
reader.Read();
TextBox txt1 = new TextBox();
Point txtyer = new Point(x, y);
txt1.Location = txtyer;
txt1.Text = reader["sikicerik"].ToString();
txt1.Name = i.ToString();
y = y + 25;
panel1.Controls.Add(txt1);
}
The reason why you may not be able to view all TextBoxes is that you are updating both x and y coordinates of the TextBox. What you may probably want to do is just increment the y coordinate as above and all the TextBoxes would appear vertically. You may also want to resize your form so that the TextBoxes don't get hidden.
Also, as #Steve mentioned in the comments above (which I missed), you need to assign the location of the new TextBoxes created as is done in the code above.
I suppose that you get the FieldCount from the reader variable.
Now in this context you should loop over the read and inside the loop create the textbox for each field retrieved by the original query.
Finally, if you don't set the Location property of the TextBoxes they will be created each on top of the others and you see only the topmost one with the latest value obtained in the loop
// Get the number of fields present in the reader....
int count = reader.FieldCount;
// Read one record at time
while(reader.Read())
{
// Create a textbox for each field in the record
for (int i = 0; i < count; i++)
{
TextBox txt1 = new TextBox();
// Set its location on screen
// Probably if you have many fields you need to
// use a better algorithm to calculate x,y position
txt1.Location = new Point(x, y);
txt1.Text = reader[i].ToString();
txt1.Name = i.ToString();
x = x + 25;
y = y + 25;
panel1.Controls.Add(txt1);
}
}
If you want to create textboxes only for the field sikicerik you have two options. The first one is the recommended because it causes less overhead on your database consist in changing the SELECT query used to build the read to
SELECT sikicerik FROM yourTableName
The other option is a simple change to your code. You don't need to loop over FieldCount because you already know that you are interested in only one field
// Read one record at time
while(oku.Read())
{
textBox1.Text = oku["soru"].ToString();
label1.Text = Form2.sinavno;
// Create the single textbox required for the only field required
TextBox txt1 = new TextBox();
// Set its location on screen
txt1.Location = new Point(x, y);
txt1.Text = oku["sikicerik"].ToString();
txt1.Name = i.ToString();
x = x + 25;
y = y + 25;
panel1.Controls.Add(txt1);
// Repeat the loop for each record.
}

Check if TextBox is created, then assign it's value

I'm trying to make a small app, for make my job easier creating definitions (new web forms aspx) via WinForms C#.
Now I have this form, where I tell the app how many textboxes I want to create.
After their creation, I want to assign to a string the textboxes values that I wrote.
private void CreateControls()
{
for (int index = 0; index < NumberOfRows; index++)
{
TextBox textBox = new TextBox();
textBox.Name = "TextBox" + (index + 1).ToString();
textBox.Size = new Size(120, 20);
textBox.Location = new Point(X, Y + 26);
ComboBox comboBox = new ComboBox();
comboBox.Name = "ComboBox" + (index + 1).ToString();
comboBox.Size = new Size(75, 20);
comboBox.Location = new Point(141, Y + 26);
comboBox.DataSource = Enum.GetNames(typeof(DataTypes));
Y += 26;
this.Controls.Add(textBox);
this.Controls.Add(comboBox);
}
}
Now, I don't know how to check if the textboxes are created, and then take their values.
Could anyone refer me something? Thanks :)!
You'll need to, on Page_Load, find those controls and grab their values. Since you gave them meaningful names when you created them, this should do the trick:
for (int index = 0; index < NumberOfRows; index++)
{
TextBox textBox = this.FindControl(
string.Format("TextBox{0}", index)) as TextBox;
if (textBox == null) { continue; } // this means it wasn't found
var text = textBox.Text;
// work with the text
}
However, if the ComboBox class you're using isn't a third-party one and it's not an ASP.NET application, the code would work for a Windows Forms application as well with a minor modification:
for (int index = 0; index < NumberOfRows; index++)
{
// you have to use the Find method of the ControlCollection
TextBox textBox = this.Controls.Find(
string.Format("TextBox{0}", index)) as TextBox;
if (textBox == null) { continue; } // this means it wasn't found
var text = textBox.Text;
// work with the text
}
I tend to agree with the community that it's probably a Windows Forms application because you can't set the Location of a standard ASP.NET control. However, if these are user controls, or third-party ones, that support those properties and render the appropriate CSS then we'd never know.
if(Page.FindControl("IDofControl") != null)
//exists
else
//does no exists

How do I find and use a programatically-created control?

I've created a number of buttons on a form based on database entries, and they work just fine. Here's the code for creating them. As you can see I've given them a tag:
for (int i = 0; i <= count && i < 3; i++)
{
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
this.Controls.Add(btnAdd);
}
I use these buttons for visualising a polling system. For example, I want the button to be green when everything is fine, and red when something is wrong.
So the problem I'm running into is referencing the buttons later so that I can change their properties. I've tried stuff like the following:
this.Invoke((MethodInvoker)delegate
{
// txtOutput1.Text = (result[4] == 0x00 ? "HIGH" : "LOW"); // runs on UI thread
Button foundButton = (Button)Controls.Find(buttonNumber.ToString(), true)[0];
if (result[4] == 0x00)
{
foundButton.BackColor = Color.Green;
}
else
{
foundButton.BackColor = Color.Red;
}
});
But to no avail... I've tried changing around the syntax of Controls.Find() but still have had no luck. Has anyone encountered this problem before or know what to do?
If you name your buttons when you create them then you can find them from the this.controls(...
like this
for (int i = 0; i <= count && i < 3; i++)
{
Button btnAdd = new Button();
btnAdd.Name="btn"+i;
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
this.Controls.Add(btnAdd);
}
then you can find it like this
this.Controls["btn1"].Text="New Text";
or
for (int i = 0; i <= count && i < 3; i++)
{
//**EDIT** I added some exception catching here
if (this.Controls.ContainsKey("btn"+buttonNumber))
MessageBox.Show("btn"+buttonNumber + " Does not exist");
else
this.Controls["btn"+i].Text="I am Button "+i;
}
Put these buttons in a collection and also set the name of the Control rather than using its tag.
var myButtons = new List<Button>();
var btnAdd = new Button();
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Name = i;
myButtons.Add(btnAdd);
To find the button use it.
Button foundButton = myButtons.Where(s => s.Name == buttonNumber.ToString());
Or Simply
Button foundButton = myButtons[buttonNumber];
In your case I would use a simple Dictionary to store and retrieve the buttons.
declaration:
IDictionary<int, Button> kpiButtons = new Dictionary<int, Button>();
usage:
Button btnFound = kpiButtons[i];
#Asif is right, but if you really want to utilize tag you can use next
var button = (from c in Controls.OfType<Button>()
where (c.Tag is int) && (int)c.Tag == buttonNumber
select c).FirstOrDefault();
I'd rather create small helper class with number, button reference and logic and keep collection of it on the form.

Categories

Resources