Cannot reference dynamically referenced controls - c#

I am using the instructions found here to access a few textboxes that are already in my Winform. For some reason I get the error:
Object reference not set to an instance of an object
I am quite sure that the code is correct but as soon as I try to access any property of the control I get that error. My code is below - can anybody spot what I am doing wrong?
TextBox textbox = this.Controls["txtLiveBlock" + ((i + 1) * (j + 1)).ToString()] as TextBox;
textbox.Text = "TESTING";
Note that my Textbox is called "txtLiveBlock1" and i = 0, j = 0. I have even tried sending the section txtLiveBlock" + ((i + 1) * (j + 1) to a MessageBox and I get "txtLiveBlock1" back.

If the control is inside another container control, like a panel or a TabPage, you would have to reference that container control:
TextBox textbox = tabPage1.Controls["txtLiveBlock" + ((i + 1) * (j + 1)).ToString()] as TextBox;
textbox.Text = "TESTING";

Break it down:
string name = "txtLiveBlock" + ((i + 1) * (j + 1)).ToString();
Control ctrl = this.Controls[name]; // returns null if the control is not found
TextBox textbox = ctrl as TextBox; // returns null if ctrl is not a TextBox
textbox.Text = "TESTING"; // if textbox is null, throws NullReferenceException
Step through this code: where does it break?
You are getting a NullReferenceException on the last line, which means that textbox is ending up as null. This could happen in a couple of ways:
this.Controls[name] returns null if the control is not in the list of controls on the form. Note that controls inside of other controls are not in this list - e.g. controls in a panel are in that panel's Controls list, not the form's.
ctrl as TextBox returns null if the ctrl is not actually a TextBox.
If you step through the code and mouse-over the variables as you go, you should be able to see what is happening.

Where are you calling this code? If it's in the constructor before InitializeComponent() gets called, then the Controls collection is empty at this point (although if it's empty, I would expect a KeyNotFoundException instead of returning a null value, but I'm not in a position to test it right now).

Related

I have multiple text boxes (tbVarName1, tbVarName2, ... tbVarName[n]) and I want to increment them in a for loop to assign the text to something

I have a multiple text boxes that I want to assign their string content to a variable however I'm not sure how to increment the text boxes. They are named, tbVarName1, tbVarname2, et cetera. Below is the for loop I have, right now I just have tbVarName1 hard coded in.
I have researched some of what other people have done and have only found tips for doing it in VB.
for(seriesIndex = 1; seriesIndex <= 4; seriesIndex++)
{
dataChart.Series["Variable " + seriesIndex].LegendText = tbVarName1.Text
}
At the end of this I would like the the legends to be updated to what's in the text boxes
Another way to do it is using the Controls collection from the Form (assuming that all TextBoxes are direct children of the form)
var ctrl = this.Controls.OfType<TextBox>();
for(seriesIndex = 1; seriesIndex <= 4; seriesIndex++)
{
TextBox t = ctrl.FirstOrDefault(c => c.Name == "tbVarName" + i);
if(t != null) dataChart.Series["Variable " + seriesIndex].LegendText = t.Text;
}
This will not require an array but you could end with a bigger loop if you have many controls of type textbox and it is not worth the effort if the TextBox are children of different containers (panels, groupboxes)
There are various ways to do this, one way is to add the controls into an array, for example:
var controls = new [] { tbVarName1, tbVarName2, tbVarName3 };
And now you can access them by index:
for(seriesIndex = 1; seriesIndex <= 4; seriesIndex++)
{
dataChart.Series["Variable " + seriesIndex].LegendText = controls[seriesIndex - 1].Text;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Like this
// Note: arrays start at zero
}

Is there an equivalent of the HTML function getElementById() for in C# for Form Controls? [duplicate]

This question already has answers here:
Get a Windows Forms control by name in C#
(14 answers)
Closed 3 years ago.
We are supposed to code Conway's Game Of Life in a C# Winform application. So, I created a 10 by 10 "game board" out of buttons. Pink means dead, blue means alive. And for ease of access, each button is named after its coordinates on the game board (e.g. x1y1, x10y9, x5y5). But then I realized I have no idea how to loop through 100 form controls and perform do stuff to each individual according to their color/value. In HTML/Javascript, I used the following loop:
for(row = 1; row <= 10; row++){
board[row] = new Array(10); //creates a 10 by 10 matrix to store the board's values in it.
for(col = 1; col <= 10; col++){
var position = "x" + row + "y" + col;
var val = parseInt(document.board.elements[position].value);
board[row][col] = val;
}
}
So the question is this: Is there a way to call form controls through strings not variable names? Something like Form1.getControlByName(string) or something?
You could set the Name property of each Button control you create. E.g. let that button is a reference to a Button control:
button.Name = "11";
Then you could use Control.ControlCollection.Find method to find the button control you are looking for as below:
Button button = this.Controls.Find("11", true).FirstOrDefault() as Button;
The this is a reference to the Form instance. You need to call FirstOrDefault, since Find returns an array of the Controls, whose name is "11". Last you have to use the as operator to convert the Control object to a Button. If conversion fails the value of button is null. So after this conversion you have to check if it is not null:
if(button != null)
{
// place here your code
}

Save Richtextbox text in TabControl to a file

I have a code that will write me all the text from all RichTextBoxes that are in the TabPage. The problem is that it does not save any text.
string projectFile = projectPathFolder + #"\" + projectName + #"\" + projectName + ".project";
for (int i = 0; i < tabControl1.RowCount; i++)
{
RichTextBox richText = tabControl1.Controls[i] as RichTextBox;
using (var stream = new StreamWriter(File.Create(projectFile)))
{
stream.Write(scintilla.Text);
}
File.WriteAllText(projectFile, "// " + tabControl1.TabPages[i].Text + "\n\n" + richText.Text, Encoding.UTF8);
}
RowCount is the wrong property to use as it returns the number of rows, not tabs. E.g. you could have 6 tabs but RowCount may still only be 1 if the tabs are narrow and the control wide.
Firstly, are you trying to enumerate all pages in the TabControl? If that's the case, use TabCount in your for loop, not RowCount
for (int i = 0; i < tabControl1.TabCount; i++)
If you only want to use the current tab, then get rid of the loop and just use tabControl1.SelectedTab.
Secondly, the line trying to get the RichTextBox is never going to work. A TabControl can only host TabPage instances, and trying to access RichTextBox instances via the Controls property is simply not going to work. You will need to get the TabPage first and then either enumerate the controls on it to find the RichTextBox, or if there is only a single control per page then you could directly access it.
RichTextBox richText = tabControl1.TabPages[i].Controls[0] as RichTextBox;
I'm not at all sure why you have that using block to write content to projectFile - it's just going to get overwritten by the call to File.WriteAllText.
As an aside, joining paths by hand is bad practice - try using Path.Combine, e.g. Path.Combine(projectPathFolder, projectName, projectName + ".project");
Edit: One other point. You're using as RichTextBox, which generally means you accept the cast may not be valid and return null. However, you're not doing an explicit null check on that result. If you expect failure, then wrap the File.WriteAllText statement in a null check. If you don't expect failure, then make the cast explicit and let it crash at the point of the cast - better to fail early than later.
RichTextBox richText = (RichTextBox)tabControl1.TabPages[i].Controls[0];
Hope this helps.

Populating consecutive Textboxes and Labels w.r.t the Index of the value in a collection

I have, in a form, consecutive textboxes and labels named tb1,tb2,tb3... and label1,label2,label3....
I have a dictionary holding number of Key value pairs.
How to populate the labels and textboxes corresponding to the value pairs in the dictionary?
Eg: dic.Key[1] -> label1 and dic.value[1] to tb1... like that.
I don't get any idea to try this.
In the other answer it is suggested to create a collection of labels and textboxes. My concern with that approach is that a developer may forget to do that or the order may get changed.
Every control has the Name property which stores that control's name. This property is set by Visual Studio. In your code, if you are not playing (read changing) with the Name property of controls, you can use the below code to achieve what you wanted to.
for(int i = 0; i < dic.Count; i++)
{
// As Control.Find returns an array of controls whose name match the specified string,
// in this example I had picked the first control
// you can make it more robust by checking
// - the number of controls returned,
// - the type of control, etc
TextBox txt = (TextBox) this.Controls.Find("tb" + (i + 1).ToString(), true)[0];
Label lbl = (Label) this.Controls.Find("label" + (i + 1).ToString(), true)[0];
txt.Text = dic[i].Value;
lbl.Text = dic[i].Key;
}
Your best bet would simply be to initialize a List (or two) in your form's constructor, putting all your labels and text boxes inside, so you can check them while looping through your dictionary.
private List<Label> labels = new List<Label>();
private List<TextBox> textBoxes = new List<TextBox>();
public MyForm()
{
labels.Add(myLabel1);
labels.Add(myLabel2);
labels.Add(myLabel3);
textBoxes.Add(myTB1);
textBoxes.Add(myTB2);
textBoxes.Add(myTB3);
}
private void addValuesFromDictionary(Dictionary<string, string> dic)
{
for (int i = 0; i < dic.Count; i++)
{
labels[i].Text = dic[i].Key;
textBoxes[i].Text = dic[i].Value;
}
}

Method to remove text boxes from panel

I have a method in a windows form application that tries to remove 2 text boxes from a panel.
In the method, I loop through all the controls in the panel. There are always supposed to be 2 panels removed and added together, but when removing, it randomly removes 1 or 2 containers when I press the button.
Here is the code to remove the textboxes:
private void removeRows()
{
string descName = "Desc" + (textBoxCounter - 1).ToString();
string costName = "Cost" + (textBoxCounter - 1).ToString();
if (textBoxCounter >= 0)
{
foreach (Control c in costItems.Controls)
{
if (c.Name == descName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
if(c.Name == costName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
}
// Decrement the counter
// This happens only once since two controls need to be removed
if (textBoxCounter == 0)
textBoxCounter = 0;
else
textBoxCounter--;
}
else
MessageBox.Show("There are no more rows to remove", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = panel.Controls.Count.ToString();
}
Here is the code to add a button:
private void addRows(string desc, string cost)
{
if (textBoxCounter >= maxExpenses)
{
MessageBox.Show("Maximum number of expenses entered", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
TextBox Desc = new TextBox();
TextBox Cost = new TextBox();
// Give the text boxes names
Desc.Name = "Desc" + textBoxCounter.ToString();
Cost.Name = "Cost" + textBoxCounter.ToString();
// Format the text boxes
Desc.Width = panel.Width / 2;
Cost.Width = panel.Width / 4;
// Add the items to the costItems panel
panel.Controls.Add(expenseDesc);
panel.Controls.Add(expenseCost);
// Add the items to the expenses dictionary
panel.Add(Desc, Cost);
// Increment the text box counter variable
textBoxCounter++;
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = costItems.Controls.Count.ToString();
}
}
Some info to know.
There will always be 2 textboxes added and removed, they relate to each other.
The textBoxCounter is initialized to 0, so the first two boxe names will be "Desc0" and "Cost0".
When I press the button to remove rows the first time, one text box is removed, and then if i press it again it might remove 2, it might only remove 1.
I tried debugging and I noticed that the foreach loop that iterates over all the controls in the panel seems to loop one time short of the full number of controls.
Any help with my code would be great.
Your problem is caused by the foreach, modifying the collection in foreach may cause some unexpected behavior. You just want to remove the TextBoxes with names being known beforehand, so why not using the method ControlCollection.RemoveByKey?
If you want to remove the last added textBoxes (Desc... and Cost...) do this:
panel.Controls.RemoveByKey(descName);
panel.Controls.RemoveByKey(costName);
If you want to remove all the added textBoxes (suppose you have other kinds of TextBoxes, otherwise we can use a little LINQ to remove all the textboxes easily):
for(int i = 0; i < textBoxCounter; i++){
panel.Controls.RemoveByKey("Desc" + i);
panel.Controls.RemoveByKey("Cost" + i);
}
Your code has two problems: you are disposing something you cannot dispose and you are iterating through a collection (which you are modifying) in the wrong way. You can delete all the Controls by doing:
panel.Controls.Clear();
Or iterating backwards by relying on the indices:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls.RemoveAt(i);
}
Regarding the Dispose, you can use it if you wish but don't need to use Remove:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls[i].Dispose();
}
PS: I asked something identical to this and got -6. One of the reasons for maintaining this question was precisely being helpful to others (I saw the code you are using to delete controls in internet and I knew that quite a few people were using it). Pretty ironical, indeed.

Categories

Resources