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

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.)

Related

Update NumericUpDown.Maximum from corresponding txt.text control using for each loop

As newbie in VBA.net i want to solve following.
I have a form with 38 text controls in 4 groupboxes. These get filled by clicking a row in a datagridview. I have 38 corresponding NUD's where i want the maximum to be equal to its corresponding text.
A 'pair' is always in one of the 4 groupboxes. Besides that there are also textboxes on the form itself to control the DGV.
I have a naming convention that makes it possible to match them easily . NUDGeel corresponds with txtGeel , NUDRood with TxtRood, NUDGroen with txtGroen etc et
Now that update is easily done if you do them one by one
NUDGeel.maximum = txtGeel.text
NUDRood.maximum = txtRood.text
etc
What i want to achieve is that this gets done in a for each loop. (Order to prevent me typing this 38 times (and also just to 'understand' it)
I can figure out how to start the loop
Dim c As Control
For Each c In Me.Controls
If TypeName(c) = "NumericUpDown" Then
'do some magic
End If
Next
I have tried to search for the magic code, and i guess from research it is pretty simple, i just donot get it .
Anyone with an idea how to fix ?
For Each tb In Controls.OfType(Of TextBox)
Dim nud = DirectCast(Controls(tb.Name.Replace("txt", "NUD")), NumericUpDown)
If nud IsNot Nothing Then
nud.Maximum = CDec(tb.Text)
End If
Next
You don't need the If statement if there are no other TextBoxes on the form besides those with corresponding NumericUpDowns.
Private Sub SetMaximum()
Dim controlNames() As String = {
"Geel", "Rood", "Name1", "Name2", ' list all the names of your controls here excluding NUD/txt
}
For Each controlName As String In controlNames
CType(Me.Controls("NUD" & controlName),NumericUpDown).Maximum = CType(Me.Controls("txt" & controlName),TextBox).Text
Next
End Sub
This assumes that all the controls are not inside any containers. If they are, you must use the container (panel, groupbox, etc) instead of Me.

Using PreviousControl property with nested subforms and controls

I am creating a custom floating number pad so that tablet users can more easily tap numerical data into a form. I'm using toggle buttons (the idea is to eventually highlight them briefly). The following code for the number pad works on my main form's controls. When I click on the 1 button, then a 1 is put into the active control of the main form
Private Sub Form_Activate()
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusControl = Application.Screen.PreviousControl.Name
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
Dim refControl As Control
If FocusForm = "" Then End
Set refControl = Forms(FocusForm).Controls(FocusControl)
If (IsNull(refControl)) Then
refControl = "1"
Else
refControl.Text = refControl.Text & "1"
End If
refControl.SetFocus
End Sub
However, if I navigate into the subform then the Screen.PreviousControl properties return the main form's name and the subform's name, but I can't then seem to be able to refer to the subform control's name as well. Bascially I'm looking for a line that does the following
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubformControl = ?????
Similarly, my subform has its own nested subform and I would like to do the same with this
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubform2 = ?????
FocusSubform2Control = ?????
The in-built floating number pad on the tablets is very fiddly to use, hence my coding of this custom one, however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active, this would similarly be much appreciated!
If called from a subform,Screen.PreviousControlreturns the name of the mainforms subfom-control (as mainform needs to be active to get the subform control (nested in mainform) active), not the subform (but they can have same name, just change controls name and you won't get subforms name returned, but subforms control name!).
Now how to get previous control of the subform? By its.ActiveControlproperty, as form was active before, its active control must be the previous control. (if you want to get previous control, when you stay on the same subform, you need to store it in a module variable)
As there may be multiple subforms inside of subforms, we just loop while the control is a subform and set it to the next subform.ActiveControltill finished.
Why bother with names, if you can use references? This enables using multiple instances of a form.
Just store the reference to the control in Form_Activate and use it inMouseUp:
'On top of forms code module
Option Explicit
Private PreviousFormControl As Access.Control
Private Sub Form_Activate()
On Error Resume Next ' ignore error if no previous control availible
Set PreviousFormControl = Application.Screen.PreviousControl ' this stores the reference not the name!
If Err.Number <> 0 Then
'Handle error here, e.g. disable controls, show msg, ...
End If
On Error GoTo 0 'Reset error handler (not needed only to remind you not just to ignore all errors!
Do While TypeOf PreviousFormControl Is SubForm ' Loop till control is not subform
Set PreviousFormControl = PreviousFormControl.Form.ActiveControl ' if subform, previous control of it is its current ActiveControl
Loop
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
PreviousFormControl.Text = PreviousFormControl.Text & "1" ' concat "1" to controls text, no need to handle empty text as it can only be an vbNullString(""), even if Null, then Null & "1" => "" & "1" => "1"
PreviousFormControl.SetFocus
End Sub
however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active
Of course you can use any Virtual Keyboard running on windows that fit your needs. But then you may have to handle things like bring keyboard to front on wanted position.
numpad emulator was the first open source app I found on quick search, but looks goods on short test. Of course there may be better solutions out there, just search and test.

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!

Texboxes Values in specified order from tabcontrol VB

I have a vb project of an event ticket selling stuff, and at the end I need to save a text file for each purchase.
I have tabbed controls, and at the very end, all the data that needs to go in the text file (event and customer) are in one tab.
I have this code, that will read the text from each textbox, and for now for testing purposes it throws a message box with the value. It is working, the only thing is, that it displays the values in an odd order and I don't know how to have them read in the required order.
(also it wouldn't hurt, if I could add the labels before the textbox.text but I'm not that greedy :) )
For Each GenericControl In TabPurchaseTickets.Controls
If TypeOf GenericControl Is System.Windows.Forms.TextBox Then
Dim tb As TextBox = DirectCast(GenericControl, TextBox)
MsgBox(tb.Text)
End If
Next
You need to have something that helps you identify these textboxes and their values before writing them on a file.
Relying on the order of the textboxes on the tabcontrol will be a great mistake in future revision of your application. If you need to change that order older file will cause a 'versioning' problem.
You could define the Tag property at design time for each textbox with a value that helps you identify them and write your file with the tag value, a separator and the textbox value
Dim sb = new StringBuilder()
For Each GenericControl In TabPurchaseTickets.Controls.OfType(Of TextBox)
Dim tb As TextBox = DirectCast(GenericControl, TextBox)
sb.AppendFormat("{0};{1}", tb.Tag, tb.Text)
sb.AppendLine()
Next
And now write the StringBuilder.ToString to your textfile, you will end up with something like this
Name;John
Surname;McInroe
Sport;Tennis
....
In this way you could change the order of your textboxes as you like becase every value is associated to the Tag property and you could easily reload it.
Of course this is just an example and I suggest you to investigate the use of a proper database system instead of a simple file.
It is a really ugly solution, but it works. Unfortunately in this project this is plenty! :D
Thanks for the tag idea, it's nice to find out that there is another field where I can "store" text in a textbox.
anyway, here is the bodge code *note the msgbox is just for testing the output, this will be modified to save in a textfile later:
Dim labels(15) As String
Dim fields(15) As String
For Each GenericControl In TabPurchaseTickets.Controls.OfType(Of TextBox)()
Dim tb As TextBox = DirectCast(GenericControl, TextBox)
fields(tb.TabIndex) = tb.Text
labels(tb.TabIndex) = tb.Tag
Next
For i As Integer = 0 To 15
MsgBox(labels(i) & ": " & fields(i))
Next

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).