How can I use value of a string to control another control in VB.net? - 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).

Related

Saving Custom Document Properties in a Loop

I'm trying to save the values of data that have been input into my form. There are a total of about 50 different fields to save across 5 different agents, so I loaded the data into arrays.
I've tried saving the fields in a loop, but it doesn't seem to work in a loop, only if each field has a separate line, which is a lot of code and messy. The Ag1Name, Ag2Name and Ag3Name are the names of my textboxes that the user enters to populate the form.
Sub LoadAndSaveData()
NumberofAgents = 3
Dim AgentName(3) as String
AgentName(1) = Ag1Name.Value
AgentName(2) = Ag2Name.Value
AgentName(3) = Ag3Name.Value
For Count = 1 To NumberOfAgents
With ActiveDocument.CustomDocumentProperties
.Add Name:="AgentName" & Count, LinkToContent:=False, Value:=AgentName(Count), Type:=msoPropertyTypeString
End With
Next Count
End Sub
The data doesn't get saved to the Custom Document Properties when the code is set up in a loop like the above. Since there are so many values to save and all the data is already in arrays, I would much prefer to use a loop rather than write out a separate line of code for all ~50 of the values. It does seem to work when each field is saved in a separate line of code.
I think this would probably get what you want. You don't really need to count the document properties first, only increment with the ones you want to update. Hopefully the only document properties you want contain the name AgentName in it.
ReDim AgentName(0) As String
Dim P As Long
For Each c In ThisDocument.CustomDocumentProperties
If InStr(1, c.Name, "AgentName", vbTextCompare) > 0 Then
ReDim Preserve AgentName(P)
AgentName(P) = c.Value
P = P + 1
End If
Next c
As a guest I cannot post a comment here, but the code you gave works OK here.
However, there is a problem with creating legacy custom document properties programmatically, because doing that does not mark the document as "changed". When you close the document, Word does not necessarily save it and you lose the Properties and their values.
However, if you actually open up the Custom Document Property dialog, Word does then mark the document as "changed" and the Properties are saved.
So it is possible that the difference between your two scenarios is not the code, but that in one scenario you have actually opened the dialog box to check the values before closing the document and in the other you have not.
If that is the case, here, I was able to change this behaviour by adding the line
ActiveDocument.Saved = False
after setting the property values.
If you do not actually need the values to be Document Properties, it might be better either to use Document Variables, which are slightly easier to use since you can add them and modify them with exactly the same code, or perhaps by storing them in A Custom XML Part, which is harder work but can be useful if you need to extract the values somewhere where Word is not available.
You can make this even easier by looping the controls on the UserForm, testing whether the control name contains "Ag" and, if it does, create the Custom Document Property with the control's value - all in one step.
For example, the following code sample loops the controls in the UserForm. It tests whether the controls Name starts with "Ag". If it does, the CustomDocumentProperty is added with that control's value.
Sub LoadAndSaveData()
Dim ctl As MSForms.control
Dim controlName As String
For Each ctl In Me.Controls
controlName = ctl.Name
If Left(controlName, 2) = "Ag" Then
With ActiveDocument.CustomDocumentProperties
.Add Name:=controlName, LinkToContent:=False, value:=ctl.value, Type:=msoPropertyTypeString
End With
End If
Next
End Sub
I feel a little stupid... I just realized that the reason that the code wasn't working was that the variable NumberofAgents was not being calculated correctly elsewhere in my code. I've got it working now. Thanks for your thoughts!

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 should I handle filling multiple textboxes when I don't know how many of them will have data?

I'm writing an application in VB in which I need to show the user some information which will be copy and pasted into another application however limitations of the other application mean that the string needs to be split into chunks no larger than 55 characters (it's just written notes). I thought the neatest way to do this was to have several textboxes each with a 'copy to clipboard' button to make it convenient for the user.
The code I have is:
Dim invdesc As List(Of String) = Split(splitstring, 55)
txtinvDesc1.Text = invdesc(0)
txtinvDesc2.Text = invdesc(1)
txtinvDesc3.Text = invdesc(2)
...
Split uses a regular expression to return a list of several lines without breaking up words and most of the time this will return a maximum of seven results but occasionally six (my original string max length is 330) and often fewer so my original idea to fill out any strings shorter than 330 with trailing spaces won't work as it's still possible I will either miss text or call a result that isn't there.
Ideally I would just do some kind of loop that only inputs to txtinvDesc(x) while there is data available and ignores the rest (or hides them) but I don't know any way to refer to a textbox other than explicitly or how to put them in any kind of list/array.
So it's a bit of an open question in "how best can I handle this requirement?"
You can create a collection (e.g., Array or List) of TextBox like with any other type/class (as you are doing with String in your code). Sample:
Dim allTextBoxes As New List(Of TextBox)
allTextBoxes.Add(txtinvDesc1)
allTextBoxes.Add(txtinvDesc2)
allTextBoxes.Add(txtinvDesc3)
Alternatively, you might iterate through all the controls in the main form by checking its type (a textbox or not). In that case you would have to set a relationship between the given name of the textbox and the data list index, via other collection for example:
Dim mappingList As New List(Of String)
mappingList.Add("txtinvDesc1")
mappingList.Add("txtinvDesc2")
mappingList.Add("txtinvDesc3")
For Each ctr As Control In Me.Controls
If (TypeOf ctr Is TextBox AndAlso mappingList.Contains(ctr.Name)) Then
ctr.Text = invdesc(mappingList.IndexOf(ctr.Name))
End If
Next
--- CLARIFICATION (not as evident as I thought)
The proposed for each loop relies on a mapping approach, that is, it relates each element in invdesc with the corresponding TextBox name. By definition, both arrays HAVE TO have the same number of elements (otherwise the mapping system wouldn't have made any sense). This is the most efficient and overall-applicable alternative; if the names of the textboxes and invdesc have elements in common (e.g., the numbers), you might just compare the names. BUT WHEN MAPPING YOU HAVE TO ACCOUNT FOR ALL THE ELEMENTS (if there is no associated TextBox to a given item, let the value blank; but all the items have to be accounted).
If you want to index the tbs:
Private TBs as New List (of TextBox)
Early on (after FormLoad) maybe in a FormSetup:
TBs.Add(txtinvDesc1)
TBs.Add(txtinvDesc2)
TBs.Add(txtinvDesc3)
...
Then:
Dim invdesc As List(Of String) = Split(splitstring, 55)
For n As Integer = 0 To invdesc.Count-1
TBs(n).Text = invdesc(n)
Next
' handle the varying 7th TB:
For n As Integer = invdesc.Count-1 To TBs.Count - 1
TBs(n).Enabled = False
TBs(n).Text =""
Next
Or a For/Each:
Dim ndx As Integer = 0
For Each tb As TextBox In TBs
tb.Text = invdesc(ndx)
ndx += 1 ' thanks varo!
Next
Then hide/disable or at least clear the text from any empty ones.
If it turns out there are always 6 you really only need an if statement:
txtinvDesc1.Text = invdesc(0)
txtinvDesc2.Text = invdesc(1)
txtinvDesc3.Text = invdesc(2)
...
If incDesc.Count-1 = 6 Then
txtinvDesc7.Text = invdesc(6)
Else
txtinvDesc7.Enabled= False
txtinvDesc7.Text = ""
End If
I would change the TB names to start at txtinvDesc0.Text to avoid getting confused (as I may have)
Use multiline textbox and in OnKeyPress event force 55 chars per line. You can find subclassed TextBox with that feature in this SO answer :
https://stackoverflow.com/a/17082189/351383

Creating VB.Net forms via code instead of in design mode

I'm using netzero hardware to manage the contents of a number of monitors. My present solution creates a form in VB.Net that has a pixel offset corresponding to where I've placed the Monitors in display management in the control panel. Each monitor has a dedicated form, and in each form are various objects.
The annoyance is that each form must be individually created (so far as I know) at design time. I can't make an array of forms, coupled with an array of offsets and assign all the properties through code.
There ought to be a way to do this...it would simplify my coding and project management.
What I see on MSDN is either over my head or not helpful.
I haven't tested this in hardware yet, but it does compile w/o error:
Public Sub makeform()
Dim MonitorForm(21) As Form
Dim MPictureBoxes(21) As PictureBox
Dim a As Integer
For i As Integer = 0 To n 'up to 21
MonitorForm(i) = New Form
MonitorForm(i).Name = "Form" & (i + 1)
MonitorForm(i).Text = "Form" & (i + 1)
MonitorForm(i).Controls.Add(MPictureBoxes(i))
MonitorForm(i).Location= new Point (x(i), y(i))
With MPictureBoxes(i)
.Name = "Picture Box " & Convert.ToString(i)
.Image = Image.FromFile(CurrentPic(i))
.Location = New System.Drawing.Point(0, 0)
.Size = New Size(1920, 1080)
' Note you can set more of the PicBox's Properties here
End With
Next
End Sub
Where I had gone wrong in my attempts at this was trying to do it this way
Dim Monitor(21) as New Form
That doesn't work, and the difference between Dim Monitor(21) as Form followed by monitor(i)= new Form
was simply too subtle for my present understand of classes, namespaces etc.
.
Well, I've had to give up on this approach and go back to creating n forms at design time (which means that they have names of form2...form22, putting each of them at manual start positions in design mode. There just doesn't seem to be a way to do this with an array of forms. So the code I have built around the messiness of forms2...forms22 works just fine, it's just going to be messy to maintain and elaborate on.
The solution to this may lie in system.screen classes, but the documentation on this is too advanced for me and I'm not finding good code examples for anything other than extracting data about how many screens there are - nothing about writing to them.
This is very easy in code. You want to make many instances of the same form. In this case, I have created a form in the designer called frmTest and I create many instances in code called frmNew:
Public Sub Main()
For x = 100 To 400 Step 100
For y = 100 To 700 Step 200
Dim frmNew As New frmTest
frmNew.Show()
frmNew.Top = x
frmNew.Left = y
frmNew.Height = 100
frmNew.Width = 200
Next
Next
End Sub
I have just used two loops to increment x and y values, but you could do this from a database or config file easily enough.

Dynamically looping through picture box controls in Visual Basic 2010 does not seem to follow any order

In a Visual Basic 2010 form application I have the below code snippet:
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
ctlControl.image = Nothing
End If
Next ctlControl
My problem is when it loops through the controls it does not start with the top left control and it seems to go over each picture box in random order.
How do I control the order of which picture box is updated next. Is there a property similar to tab index(in VB 6) which I can manipulate to control the order in which picture boxes are updated by my loop?
As a more proper and sure way, I would get each picture box, keep handles and locations of them, then sort them according to their location. Now they are ready to use. Here is an example:
Public Class Form1
Structure Pbox
Dim handle As IntPtr
Dim top As Integer
Dim left As Integer
End Structure
Dim pboxlist As New List(Of Pbox)
Sub ClearImages()
pboxlist.Clear()
For Each c As Control In Me.Controls
If TypeName(c) = "PictureBox" Then
Dim x As New Pbox
x.top = c.Top
x.left = c.Left
x.handle = c.Handle
End If
Next
pboxlist.OrderByDescending(Function(a) a.top).ThenByDescending(Function(a) a.left)
For Each item In pboxlist
Dim x As PictureBox = PictureBox.FromHandle(item.handle)
x.Image = Nothing
Next
End Sub
End Class
Another approach is using a good naming, so that you can use their names to sort them. For instance, PictureBox1 will come before PictureBox2 if you sort. So you should use PictureBox1 for the very top and left one and PictureBox2 for the next one and so on...
EDIT: Using Tag property, as John Bustos suggested, instead of names is an easier and better idea. So without getting lost in names, you can sort picture boxes according to their Tags which are defined by you.
As some of the other guys have said you could use the TAG property which is probably the BEST shot, whenyou are dynamically creating the picture boxes use a counter and add the counter value to the TAG property. if you added the picture boxes manually then simply start with the top left and work towards the right and add a value in the TAG property field of each one starting with 1 and increasing by one each time and continue until the row is completed then carry on with the next row.
Finally when your ready to loop through the picture boxes simply follow the pattern below..
'Calc number of picture boxes
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
Counter = Counter + 1
End If
Next ctlControl
ThisBox = 1
Do
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
If CInt(ctlControl.Tag) = ThisBox Then
CLEAR-YOUR-IMAGE-HERE
ThisBox = ThisBox + 1
End If
End If
Next ctlControl
Loop Until ThisBox = Counter
Note:
Its IMPORTANT your numbers that you place in the TAG property are consecutive or you will become forver stuck in the DO-LOOP!!!
The order of the controls was determined by the order they were added to the panel and not tabstop index. You can change that by carefully reorganizing the order they were added to the panel in the form's designer file, though I'd not recommend it.
The PictureBox control has a Text property you can use instead of Tag.
It doesn't come up in Intellisense because it's an infrastructure property, but it's there.
http://msdn.microsoft.com/en-us/library/hc9k45f4(v=vs.110).aspx
(I wanted to comment on Zaf Khan's answer but I don't have the rep, yet.)