Texboxes Values in specified order from tabcontrol VB - vb.net

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

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.

Detect if combobox has been modified in the last Xs [duplicate]

Good morning!
I have a "fancy" search function in Microsoft Access where the list of possible options shrinks as you type in the search field. Unfortunately the computer and server can't keep up with these rapid requeries of the data.
Currently the command to requery with the field in the 'onchange' function of the search box. I'd like to add a delay so it only runs the requery when the search box has not changed for a second. Thus if someone types in a 8 letter word, it isn't running 8 requeries.
The current idea I have for it, which I know there must be something better, is..
"On change, set search box value to X and wait 1 second. After 1 second, if X = search box value, run the requery. An issue is that it would be rapidly rewriting the X value and have a 'wait' command floating for each letter.
Hopefully there's a way to write an event trigger of "When field X has changed, but not changed for the past second."
Thank you!
As requested, here is my current code
'Create a string (text) variable
Dim vSearchString As String
'Populate the string variable with the text entered in the Text Box SearchFor
vSearchString = SearchFor.Text
'Pass the value contained in the string variable to the hidden text box SrchText,
'that is used as the sear4ch criteria for the Query QRY_SearchAll
SrchText = vSearchString
'Requery the List Box to show the latest results for the text entered in Text Box SearchFor
Me.SearchResults.Requery
Me.SearchResults2.Requery
'Tests for a trailing space and exits the sub routine at this point
'so as to preserve the trailing space, which would be lost if focus was shifted from Text Box SearchFor
If Len(Me.SrchText) <> 0 And InStr(Len(SrchText), SrchText, " ", vbTextCompare) Then
'Set the focus on the first item in the list box
Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor,
'and restores trailing space lost when focus is shifted to the list box
Me.SearchFor = vSearchString
Me.SearchFor.SetFocus
Me.SearchFor.SelStart = Me.SearchFor.SelLength
Exit Sub
End If
'Set the focus on the first item in the list box
' Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor
Me.SearchFor.SetFocus
If Not IsNull(Len(Me.SearchFor)) Then
Me.SearchFor.SelStart = Len(Me.SearchFor)
End If
Obviously this is not MY code, it's from somewhere on the interweb. It works fantastic for databases stored locally, but everything is moving to our Sharepoint server which is running on a 386 in a moldy basement powered by a narcoleptic gerbil.
You can simply use the Timer of the current form. No need for a separate form or anything.
Private Sub DoSearch()
' Your current code
' but you should look into removing as many "Requery" from there as possible!
End Sub
Private Sub SearchFor_Change()
' Wait for x Milliseconds until the search is started.
' Each new change restarts the timer interval.
' Use 1000 (1 s) for slow typists or a really slow server
' 200 ms feels right for a normal typist
Me.TimerInterval = 200
End Sub
Private Sub Form_Timer()
' Disable timer (will be enabled by the next SearchFor_Change)
Me.TimerInterval = 0
' Now run the search
DoSearch
End Sub
Note: you may need to move some of the cursor-handling code from DoSearch() to SearchFor_Change(), specifically:
Me.SearchFor.SelStart = Len(Me.SearchFor)
Assign a shortcut key like (Ctrl+ J) to the logic in on change event and call it on demand once you have finished typing search keyword.
Remove on change event.
Create other procedure which has the logic of on change event and assign a shortcut key
Press shortcut to get search suggestion
Other approach
Add below validation to Change event which will check for length of string and will trigger only if length of string is >=8
Private Sub txtSearch_Change()
If Len(Nz(txtSearch.Text, 0)) >= 8 Then
End If
End Sub
I'm going a little outside my comfort area, since I hardly use MS Access forms, but why are you bothering the Server/Database so much? In my experience, each query costs the same amount of time, whether it returns 1 record or 100,000 records.
So even before the user types anything, why don't you just do a single query to return a sorted list. After that, it takes almost no time to use VBA to process the results and find everything in the list that starts with whatever the user types in (it's sorted after all).
Except for the initial load, users who are local to the database or on the other side of the world will experience the same snappy response from your interface.
----------
Like I said, I haven't messed with Access Forms a lot, so this is more of a strict VBA solution. Maybe there is a better way to do it without going outside the Access Forms box that someone could enlighten us with.
You should basically just call LoadItemList when you load the form, or whenever you need to.
Public dbConn As ADODB.Connection
Private ItemList As Variant
Private RecordCount As Long
Sub LoadItemList()
Dim SQL As String
Dim RS As New ADODB.Recordset
SQL = "SELECT T.Name FROM Table T"
Set RS = dbConn.Execute(SQL)
If Not RS.EOF Then
ItemList = RS.GetRows
RecordCount = UBound(ItemList, 2) - LBound(ItemList, 2) + 1
End If
End Sub
Then replace DoCmd.Requery with AddItemtoCombobox SearchResults, SearchFor.Text
Sub AddItemtoCombobox(Control As ComboBox, Filter As String)
Dim Index As Long
Control.Clear
If Not IsEmpty(ItemList) Then
For Index = 0 To RecordCount - 1
If ItemList(Index) Like Filter Then Control.AddItem ItemList(Index)
Next
End If
End Sub
Again, maybe there is a better way that is built into Access...
The technical term that you're looking for is debounce.
What you can do is on your on change event, keep track of the current search string
in terms of pseudocode.
sub onChange()
Form.timerinterval = 0
setSearchString
form.timerinterval = delay
So in terms of the explanation, if your on change is called, disable the timer. Update your search string, then reset the timer to fire after a certain amount of time. The form should be a hidden form that contains the code that you want to execute

MS Access: Action "onchange" event after a delay?

Good morning!
I have a "fancy" search function in Microsoft Access where the list of possible options shrinks as you type in the search field. Unfortunately the computer and server can't keep up with these rapid requeries of the data.
Currently the command to requery with the field in the 'onchange' function of the search box. I'd like to add a delay so it only runs the requery when the search box has not changed for a second. Thus if someone types in a 8 letter word, it isn't running 8 requeries.
The current idea I have for it, which I know there must be something better, is..
"On change, set search box value to X and wait 1 second. After 1 second, if X = search box value, run the requery. An issue is that it would be rapidly rewriting the X value and have a 'wait' command floating for each letter.
Hopefully there's a way to write an event trigger of "When field X has changed, but not changed for the past second."
Thank you!
As requested, here is my current code
'Create a string (text) variable
Dim vSearchString As String
'Populate the string variable with the text entered in the Text Box SearchFor
vSearchString = SearchFor.Text
'Pass the value contained in the string variable to the hidden text box SrchText,
'that is used as the sear4ch criteria for the Query QRY_SearchAll
SrchText = vSearchString
'Requery the List Box to show the latest results for the text entered in Text Box SearchFor
Me.SearchResults.Requery
Me.SearchResults2.Requery
'Tests for a trailing space and exits the sub routine at this point
'so as to preserve the trailing space, which would be lost if focus was shifted from Text Box SearchFor
If Len(Me.SrchText) <> 0 And InStr(Len(SrchText), SrchText, " ", vbTextCompare) Then
'Set the focus on the first item in the list box
Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor,
'and restores trailing space lost when focus is shifted to the list box
Me.SearchFor = vSearchString
Me.SearchFor.SetFocus
Me.SearchFor.SelStart = Me.SearchFor.SelLength
Exit Sub
End If
'Set the focus on the first item in the list box
' Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor
Me.SearchFor.SetFocus
If Not IsNull(Len(Me.SearchFor)) Then
Me.SearchFor.SelStart = Len(Me.SearchFor)
End If
Obviously this is not MY code, it's from somewhere on the interweb. It works fantastic for databases stored locally, but everything is moving to our Sharepoint server which is running on a 386 in a moldy basement powered by a narcoleptic gerbil.
You can simply use the Timer of the current form. No need for a separate form or anything.
Private Sub DoSearch()
' Your current code
' but you should look into removing as many "Requery" from there as possible!
End Sub
Private Sub SearchFor_Change()
' Wait for x Milliseconds until the search is started.
' Each new change restarts the timer interval.
' Use 1000 (1 s) for slow typists or a really slow server
' 200 ms feels right for a normal typist
Me.TimerInterval = 200
End Sub
Private Sub Form_Timer()
' Disable timer (will be enabled by the next SearchFor_Change)
Me.TimerInterval = 0
' Now run the search
DoSearch
End Sub
Note: you may need to move some of the cursor-handling code from DoSearch() to SearchFor_Change(), specifically:
Me.SearchFor.SelStart = Len(Me.SearchFor)
Assign a shortcut key like (Ctrl+ J) to the logic in on change event and call it on demand once you have finished typing search keyword.
Remove on change event.
Create other procedure which has the logic of on change event and assign a shortcut key
Press shortcut to get search suggestion
Other approach
Add below validation to Change event which will check for length of string and will trigger only if length of string is >=8
Private Sub txtSearch_Change()
If Len(Nz(txtSearch.Text, 0)) >= 8 Then
End If
End Sub
I'm going a little outside my comfort area, since I hardly use MS Access forms, but why are you bothering the Server/Database so much? In my experience, each query costs the same amount of time, whether it returns 1 record or 100,000 records.
So even before the user types anything, why don't you just do a single query to return a sorted list. After that, it takes almost no time to use VBA to process the results and find everything in the list that starts with whatever the user types in (it's sorted after all).
Except for the initial load, users who are local to the database or on the other side of the world will experience the same snappy response from your interface.
----------
Like I said, I haven't messed with Access Forms a lot, so this is more of a strict VBA solution. Maybe there is a better way to do it without going outside the Access Forms box that someone could enlighten us with.
You should basically just call LoadItemList when you load the form, or whenever you need to.
Public dbConn As ADODB.Connection
Private ItemList As Variant
Private RecordCount As Long
Sub LoadItemList()
Dim SQL As String
Dim RS As New ADODB.Recordset
SQL = "SELECT T.Name FROM Table T"
Set RS = dbConn.Execute(SQL)
If Not RS.EOF Then
ItemList = RS.GetRows
RecordCount = UBound(ItemList, 2) - LBound(ItemList, 2) + 1
End If
End Sub
Then replace DoCmd.Requery with AddItemtoCombobox SearchResults, SearchFor.Text
Sub AddItemtoCombobox(Control As ComboBox, Filter As String)
Dim Index As Long
Control.Clear
If Not IsEmpty(ItemList) Then
For Index = 0 To RecordCount - 1
If ItemList(Index) Like Filter Then Control.AddItem ItemList(Index)
Next
End If
End Sub
Again, maybe there is a better way that is built into Access...
The technical term that you're looking for is debounce.
What you can do is on your on change event, keep track of the current search string
in terms of pseudocode.
sub onChange()
Form.timerinterval = 0
setSearchString
form.timerinterval = delay
So in terms of the explanation, if your on change is called, disable the timer. Update your search string, then reset the timer to fire after a certain amount of time. The form should be a hidden form that contains the code that you want to execute

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

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