VB.NET - Dynamically created controls and how to manipulate them - vb.net

If I create a control like so:
Dim fb As New Label
With fb
.Name = "newLabel"
.text = "some text"
<etc.>
Me.Controls.Add(fb)
End With
Now, if I wanted to change the text on that label during run time, I would normally do:
newLabel.text = "some other text"
Unfortunately, Visual Studio won't let me do that as 'newLabel' isn't defined until run time.
So, my question is: How do I reference a control created in such a way? (The only way I can think of is to loop through all controls until I find the one I'm looking for, but that seems a tad inefficient to me.)

'newLabel' isn't defined until run time"
That isn't really accurate. You are confusing the object with a variable used to reference the object. When you add a control to a form in the designer, VS generates code to create and configure that control. It's much the same as the code you wrote and posted. You can see it in the designer code file, which you can access if you click the 'Shoe All Files' button in the Solution Explorer. That code includes a member variable to which the created object is assigned. You then use that member variable to refer to that object in code.
If you're creating controls at run time then you generally can't declare a member variable for each one to be assigned to because you don't know how many there will be. If you do know how many there will be then you probably ought to be adding them at design time. That means that you have two options:
Declare a single member variable that will refer to a collection of controls created at run time and then access then via name or index from that.
Access them by name from the Controls collection of the form or other container control that you must add them to in order for them to be displayed.
Option 2 requires that you provide a unique name for each control when you create it. Option 1 doesn't require a name at all, although it doesn't preclude one.
Option 1 might look like this:
At the class level:
Private labels As New List(Of Label)
In a method somewhere:
For i = 0 To 9
Dim lbl As New Label
labels.Add(lbl)
Controls.Add(lbl)
Next
Later:
Dim lbl = labels(recordIndex)
Option 2 might look like this:
In a method somewhere:
For i = 0 To 9
Dim lbl As New Label With {.Name = "titleLabel" & i}
Controls.Add(lbl)
Next
Later:
Dim lbl = DirectCast(Controls("titleLabel" & recordIndex), Label)

Related

Reading checkbox name from text file

I'm trying to read a checkbox name from a txt file. I'm not sure if this is posible but i tried this:
Me.(My.Computer.FileSystem.ReadAllText(Application.StartupPath & "\Settings\language.txt")).Checked = 1
Sadly it didnt work, i get the error "identifier expected" and it underlines the dot after the Me.
The basic problem here is understanding what things are resolved at compile time vs what things are resolved at run time.
A checkbox name is an identifier that must resolve at compile time. You see a nice name like CheckBox1 in your source code, but when the program actually runs all you really have is a reference consisting mainly of a number representing an offset into your program's memory address space. The CheckBox1 name as a variable no longer exists; only the object reference remains.
On the other hand, StartupPath and the contents of the text file are only strings. They are not identifiers, and their values are not known until much later, when the program is already running.
The good news is, in the case of WinForms controls the variable name is preserved as data in the Control object, and you can search for it. You just need to use a method that will look at your Control objects, like this:
Dim language As String = My.Computer.FileSystem.ReadAllText(Path.Combine(Application.StartupPath, "\Settings\language.txt"))
Dim languageBox as CheckBox = DirectCast(Me.Controls(language), CheckBox)
Or maybe this:
Dim languageBox as CheckBox = Me.Controls.OfType(Of CheckBox)().FirstOrDefault(Function(box) box.Name = language)
Or maybe your checkbox is nested within a GroupBox or Panel. In that case, you need to change Me for the name of the GroupBox, Panel, or other container control that directly holds the checkbox.
what is the name of checkbox in form ?...
Dim _chkbxName$ = My.Computer.FileSystem.ReadAllText(Application.StartupPath & "\Settings\language.txt")
For Each cnt As Control In Me.Controls
If TypeOf cnt Is CheckBox Then
If cnt.Name = _chkbxName Then
CType(cnt, CheckBox).Checked = True
Exit For
End If
End If
Next

Is there a simpler way to process check boxes?

In vb.net, I have a form that has a set of four Check Boxes. Each Check Box signifies that (when checked) the user wants to add a special instruction to their order. The code looks like this:
If SpecialInstruction1CheckBox.Checked Then
AddSpecialInstruction(SPECIAL_INSTRUCTION_1_String)
End If
If SpecialInstruction2CheckBox.Checked Then
AddSpecialInstruction(SPECIAL_INSTRUCTION_2_String)
End If
If SpecialInstruction3CheckBox.Checked Then
AddSpecialInstruction(SPECIAL_INSTRUCTION_3_String)
End If
If SpecialInstruction4CheckBox.Checked Then
AddSpecialInstruction(SPECIAL_INSTRUCTION_4_String)
End If
I have a feeling that this code is unnecessarily verbose, feels repetitive, and could be simplified. How would I go about doing this, or is this not as "wrong" as it feels?
The first problem is that your special instructions should not be stored in separate variables. They should be stored in an array or some other kind of list. Then you could access them by index (e.g. specialInstructions(1)).
Then you can loop through the check boxes by index like this:
For i As Integer = 1 to 4
Dim box As CheckBox = DirectCast(Me.Controls("SpecialInstruction" & i.ToString() & "CheckBox"), CheckBox)
If box.Checked Then list.Add(specialInstructions(i))
Next
Alternatively, you could store references to your check boxes in an array and then loop through them more easily, for instance:
Dim checkBoxes() As CheckBox = {
SpecialInstruction1CheckBox,
SpecialInstruction2CheckBox,
SpecialInstruction3CheckBox,
SpecialInstruction4CheckBox}
' ...
For i As Integer = 0 to checkBoxes.Length - 1
If checkBoxes(i).Checked Then list.Add(specialInstructions(i))
Next
Another option would be to store the special instructions in the Tag property of each check box, then you could just retrieve the value from the control, like this:
For Each i As CheckBox In checkBoxes
If i.Checked Then list.Add(i.Tag)
Next
But that only makes sense if you don't need to reuse those special instructions values elsewhere in your code.
Actually the code isn't that bad in itself. It mainly depends on what AddSpecialInstruction does, exactly. Depending on your specifics it might be better to pass it a list of string instructions instead:
Dim list As New List(Of String)
If SpecialInstruction1CheckBox.Checked Then list.Add(SPECIAL_INSTRUCTION_1_String)
If SpecialInstruction2CheckBox.Checked Then list.Add(SPECIAL_INSTRUCTION_2_String)
If SpecialInstruction3CheckBox.Checked Then list.Add(SPECIAL_INSTRUCTION_3_String)
If SpecialInstruction4CheckBox.Checked Then list.Add(SPECIAL_INSTRUCTION_4_String)
AddSpecialInstructions(list)
Since you also required code shrinking, I made If statements holding on one line. Shorter variable names would help on that too.

How can I use value of a string to control another control in VB.net?

I have been playing around with some code, and I have made easily 50+ controls that all are labeled: PictureBox[XCoordinate]_[YCorrdinate] (Replacing the brackets and contents with the coordinates of them on a little grid I made.)
The problem with this is it is a real pain to use a control when doing loops to update all the picture boxes. I want to know how to do code like:
'This code assumes that the picture boxes are all initialized.
Dim XCoordiante As Integer = 5
Dim YCorrdinate As Integer = 2
PictureBox[XCoordinate]_[YCoordiante].Image = [Put Image Here]
I am going to put this within a loop. Is there a way that I can do this without manually typing it all and risking missing something within a case statement? And also, I would have to retype it for every different kind of change I want to make (ex: tag or error image).
Would a pointer somehow help? I don't really know how to do this, but it would be really helpful if possible.
When you create them, save them to a List:
Private pList As New List(Of PictureBox)
Dim pic As New PictureBox
With Pic
.Location = ...
' etc
End With
Me.Controls.Add(pic)
pList.Add(pic)
Assuming they are created in some sort of order:
For n As integer = 0 To pList.Count = 1
' add code to look at Plist(n).X and .Y to determine what to do (?)
Plist(n).Image = ...
Next n
If there is more info to capture, create a custom class of a PicBox and the other info, and make the list a List(Of myPicClass).

How can I change a toolbox item property by referring to the item through a variable?

Sorry for the confusing title - here is the code I'm using.
Example code -
If bolCorrect = False Then
intIncorrect += 1
temp3 = "picture" + CStr(intIncorrect)
temp3.Visible = True
I've got several images all, with names of picture[number-from-0-to-10], and I want them to show depending on the count of a variable.
The error it throws up is that 'Visible' is not part of 'String'. How can I get the interpreter to look at 'temp3' in this instance, and refer to the toolbox item rather than the type of the variable (e.g. string)?
You need to refer to the actual name property you have set for the picturebox control (if you are using the picturebox control)
So if your picture box control is named pb1
pb1.Image = System.Drawing.Image.FromFile("picture" + counter + ".jpg")
pb1.Visible = True
You should generally try to avoid addressing controls via strings, that’s usually just a hack around a proper solution. Instead, maintain a variable to that control, or, in your case, maintain an array of the relevant controls and access them via an index.
That said, it is possible to get a control given its name via the Form.Controls collection:
Dim ctl = Me.Controls("picture" + CStr(intIncorrect))

McAfee deletes code from VBA module

I am trying to program an Excel module where it dynamically inserts code in new objects in a form that is created at design time.
I am using this code where "Code" contains a string with the actual code that should go into the DstrFiles object.
Dim DstrFiles As Object
Set DstrFiles = ThisWorkbook.VBProject.VBComponents("DistributeFiles")
With DstrFiles.CodeModule
.InsertLines .CountOfLines + 1, Code
End With
My problem is that when I use the .InsertLines, McAfee removes the entire Code from my module, is there a way to work around this?
First I create the label with:
Form1.Controls.Add("Forms.Label.1", "Label1", True)
Then I use the .InsertLines to create some code to go with the Label.
For instance, I want the background color of the label to turn red when someone clicks on it. This has been very easy to accomplish with the ".InsertLines".
An ugly way to work around this is to just create a bunch of code beforehand that is ready in the background and then limit the amount of labels that may be created on the fly. - I hope it won't come to that.
I have been googeling around, and this seems to be a known problem with McAfee.
Do anyone know a way to create a dynamic user form that can add code to new labels or button that are added with the Contrls.Add method?
You should not be generating new labels by writing code that creates the controls.
You should be using the .Add method on the Controls collection to create new labels.
For example:
UserForm1.Controls.Add("Forms.Label.1", "foo", True)
You can use WithEvents to get the events.
For example, in UserForm1,
Public WithEvents a As MSForms.Label
Private Sub a_Click()
MsgBox "label clicked"
End Sub
Private Sub CommandButton1_Click()
Set a = UserForm1.Controls.Add("Forms.Label.1", "foo", True)
a.Visible = True
a.Caption = "Hi There"
End Sub
If you want to make a dynamic array of newly added controls, you'll need to create a little wrapper class. Sample code for that is here.
If possible I would recommend against dynamic generation of code (smells like a self-modifying program?).
It's maybe hard to say without knowing your specific problem but I bet there is a better solution using a function with the necessary parameters.
You might be able to workaround this version of McAfee. But the next version of the data-files, or another malware blocker might block you anyhow.
So you can create code like this to run on you development machine, but it will never (or only temporary) work when distributed to customers.