VB6: array of controls field, checking if it is numeric - arrays

I’m new to Visual Basic 6, so please be patient (and thorough with your answers).
I’m building a form and I need to check if the information entered in one of its fields is numeric, otherwise the program has to beep.
The field is part of an array of controls and it is named txtMyField(0)
Last thing in my code I’ve written:
Private Sub txtMyField_Change(Index As Integer)
If Not IsNumeric(txtMyField(0).Text) Then
Beep
End If
End Sub
I don’t know if this code is correct and I don’t how to call the Sub to use it in order to check the field’s value before inserting in database.
Thanks a lot for your help!

You can do this by using the Validate event. You need to ensure that the CausesValidation property is true, if it is then the Validate event will be raised for that control.
Your event might look something like this:
Private Sub txtMyField_Validate(Index As Integer, Cancel As Boolean)
If Not IsNumeric(txtMyField(Index).Text) Then
Beep
Cancel = True
End If
End Sub
This would ensure that all the controls in your control array are numeric (given that their CausesValidation property is set to True at startup). If one of the controls is empty or contains non-numeric characters you will get a Beep when the control loses focus.
Note some things here
Sounding a Beep is not really a good way to indicate a validation error. A messagebox or textbox in the form to display the error is usually a better way. The user may not hear your beep or may not understand that "a beep" means "you need to provide a number in this field".
Your code referenced control with index = 0. The event may fire for any of the controls in the control array, so checking the value of control(0) is not really a logical thing to do when you should be validating control(5) (for example).

instead of beeping you could also make your texbox only accept certain keys
for example a textbox which will only accept numeric keys and the backspace key :
Private Sub Text1_KeyPress(KeyAscii As Integer)
KeyAscii = NrOnly(KeyAscii)
End Sub
Private Function NrOnly(intKey As Integer)
Dim intReturn As Integer
intReturn = intKey
Select Case intKey
Case vbKeyBack
Case vbKey0 To vbKey9
Case Else
intReturn = 0
End Select
NrOnly = intReturn
End Function
you can add more intelligence to the NrOnly function to allow more keys, or check for certain boundaries
be careful though as the user can still use the mouse to input other data via copy&paste

Related

Winforms ObjectListView: inner OLVColumn instances Name property is empty string so I cannot show/hide columns by name

This question is an offshoot of: Localizing ObjectListView OLVColumn, impossible due to Empty Name property
For simplicity's sake, let's say my ObjectListView contains car information. User A wants to display only Make and Model columns. User B only wants to display Model and Year columns. These preferences would be saved to/loaded from an .ini file on the users' local machines.
I cannot loop through the columns of the ObjectListView and do if (col.Name == colNameFromIni) { col.Visible == true; } because the .Name property of every column is an empty string ("") and does not get serialized to the designer codebehind file. This never happens with any other Winforms control (Label, Button, etc.) They always get their .Name written to the designer codebehind.
In some sense, this is a flaw in Winforms itself, because OLVColumn inherits from System.Windows.Forms.ColumnHeader, and a traditional ListView has exactly the same problem. .Name is always an empty string for all columns.
I would like to patch our local build of ObjectListView.dll to force populate the .Name property, but I can't figure out how Winforms automagically knows the name of every control on the form. It somehow(?) knows the names of the OLVColumn objects since it can display them in the Edit Columns... dialog on the ObjectListView's context menu. I'm also a little fuzzy on where the best spot is to plug this in.
(Yes, per linked question at top I know that as a last resort, I can hardcode colXX.Name = "colXX"; for all columns in my source code, but future column additions are likely to get overlooked and a programmatic solution is much preferred.)
(See also: https://sourceforge.net/p/objectlistview/bugs/160/ : the ObjectListView author declared this a wont-fix so it is up to me (or us), I guess.)
As you point out, this is a bug which is not with the ObjectListView, but the underlying component. And a bug which is around since at least 2008! Therefore, I doubt it will ever be fixed by MS.
Actually, it is a problem with the Autogenerated code in the designer.
If you look at other components such as a button, then the autogenerated code adds a name such as this;
//
// button2
//
this.button2.Location = new System.Drawing.Point(458, 199);
this.button2.Name = "button2";
...
But for ColumnHeader (Listview) and OLVColumn (ObjectListView), then this is not done, so then you end up with this.
//
// olvColumn1
//
this.olvColumn1.AspectName = "Name";
this.olvColumn1.Text = "Name";
If you manually add the line
this.olvColumn1.Text = "olvColumn1";
Then the "problem" is solved.
Of course, you can't do this, because the designer will override the autogenerated code when you make any changes, and then you will lose these manually added lines. It is also not sustainable.
So I'm afraid you need to code around this with some kind of ugly solution. Some options are:
Use the Tag to store the name and compare against this.
Use the text instead of the name (not possible if you have multi
language support!)
Code the names column manually in the Constructor
Set the Text to be something like "ColName;ColText" and then in your
code separate these out.
I have done option 3 in the past, but only I was maintaining the code, so this was easy.
What you could do to ensure you don't have discrepancies is to add a check in your constructor to compare the actual number of columns with the number you expect (hard coded for), and throw an exception if they don't match. Also, not the best, but another way to highlight and reduce errors.
The workaround for this is to get the OLVColumns via reflection and set their column's Name property at runtime. Every OLVColumn is a form-level field, so just pick them out of the list returned by GetFields().
Dim allFieldInfos As FieldInfo() = GetType(FrmMain).GetFields(BindingFlags.NonPublic or BindingFlags.Instance)
For Each fi As FieldInfo In allFieldInfos
If fi.FieldType Is GetType(OLVColumn) Then
Dim instance As OLVColumn = fi.GetValue(Me)
For Each col As OLVColumn In fdlvMain.AllColumns
If ReferenceEquals(col, instance) Then
col.Name = fi.Name
End If
Next
End If
Next

VBA global frame array

unfortunately I was not able to answer my question with the help of Google.
I created a Userform with a frame which contains different controls (Textbox, Combobox, Listbox, Label).
Usually the inputs are saved directly to a workbook after the user presses the OK button.
But there is one special situation, where I want the user to fill in several of these Input Userforms one after another and save all the contents of the Userforms at last.
Therefore I created a global Object-Array where I saved the Input-Frames of each Userform, the user filled in.
At the end I wanted to go through all these frames and its controls and save the contents in one step.
My already existing save function uses a frame as input, so I wanted to loop through my frame array.
Here is my example code:
(conArr is a global Object Array variable)
Public Sub btnOK_Click()
conArrRow = 0
ReDim Preserve conArr(conArrRow)
Set conArr(conArrRow) = frmData.frameDataInput
Unload Me
End Sub
After the Userform is unloaded I get back to the main code.
For Each ctl In conArr(0).Controls
MsgBox ctl.Name
Next
Here I get the error at line: For Each ctl In conArr(0).Controls
Microsoft Visual Basic
Run time error -2147418113 (8000ffff)
Automation error
Catastrophic failure.
I guess the problem occurs because the form with its frame is already unloaded and does not exist anymore. If I put the part of the main code directly to the end of btnOK_Click it works.
I appreciate any help.
Thanks in advance.

Drop Down Parameter Not Returning a Value For a Query

I have a database set up which includes a query and a form, which are causing me problems. In the form, I have a drop down menu which allows you to select from a list of ID values (e.g. 1, 2, 3, ...). Once you have selected a value, you then click a button and it runs the query with a parameter of the ID number from the drop down ([Forms]![KitInfoRetrievalForm]![DropDown]).
The problem here though, is that when I select something from the drop down menu and click the button to run the query, it gives a pop up box asking for a value to substitute in for [Forms]![KitInfoRetrievalForm]![DropDown]. This leads me to believe that either the drop down menu is a null value for some reason or my pathing to it is incorrect.
This was working at one point and then stopped after a series of weird error messages from something else entirely (in the same Access project). Any help you can give me would be much appreciated.
I would recommend to replace reference to dropdown in query by function. It should eliminate this problem and also this is a workaround for old bug in Access, which exists in all recent versions: if the form/subform is in datasheet mode and you applied filter (quick filter thru user interface or using VBA), it stops reading variables with references to controls or just parameters and uses last used value.
I'm using this function for reading form/subform control value:
Public Function GetControlValue(strFormName As String, strControlName As String, Optional strSubFormControlName As Variant) As Variant
On Error Resume Next
If IsMissing(strSubFormControlName) Then
GetControlValue = Forms(strFormName).Controls(strControlName).Value
Else
GetControlValue = Forms(strFormName).Controls(strSubFormControlName).Form.Controls(strControlName).Value
End If
End Function
In your case replace [Forms]![KitInfoRetrievalForm]![DropDown] by
GetControlValue("KitInfoRetrievalForm","DropDown")

How to create an array with textboxes in Visual Basic 6.0

I am working on a very old visual basic project. I have to rewrite the load and save function of the project. Therefore I wanted to create an array of controls which have all relevant textboxes and checkboxes included. I want to iterate through that array to be able to save the data into a textfile or load from it.
I have looked on the internet of how you can define those arrays, but it doesn't seem to work for me. Maybe I am doing something wrong, cause I am not an expert in Visual Basic.
I have tried to make it work this way:
Dim tbList As TextBox = { Form1.Text1, Form1.Text3, _
Form1.Text10, Form1.Text11, Form1.Text12, Form1.Text13, _
Form2.Text1, Form2.Text3, Form2.Text4, Form2.Text5, _
Form2.Text10, Form2.Text11, Form2.Text12, Form2.Text13, _
Form3.Text1, Form3.Text3, Form3.Text4, Form3.Text5, _
Form3.Text10, Form3.Text11, Form3.Text12, Form3.Text13, _
Form3.Text17, Form3.Text18, Form3.Text19, Form3.Text20, _
Form4.Text1, _
Form5.Text1, Form5.Text2, Form5.Text3, _
Form6.Text2, _
Form7.Text2}
Or with a list:
Dim tbList As New List(Of Controls)
The thing is Visual Basic always tells me there are some kind of compiling issues. There is no real explantation for this issue in VB, so I am asking here.
Your code isn't compiling because it is vb.net code. It should go without saying (but I'll say it anyway) that vb6 and vb.net are not the same thing.
If you want to use an array, you will have to dimension the array with a number that is one less than your number of textboxes (if I counted correctly there are 32 in your example):
'// array is zero based so 0 to 31 = 32 items
Dim tbList(31) As TextBox
tbList(0) = Form1.Text1
tbList(1) = Form1.Text3
'//...I'll leave the rest as an exercise for the programmer
tbList(31) = Form7.Text2
Dim i As Integer
Dim tb As TextBox
'// To loop and work with each textbox
For i = 0 To UBound(tbList)
Set tb = tbList(i)
'// do something with tb
Next
An easier way to do it, however, is to work with a collection:
Dim tbList As New Collection
tbList.Add Form1.Text1
tbList.Add Form1.Text3
'//...I'll leave the rest as an exercise for the programmer
tbList.Add Form7.Text2
Dim tb As TextBox
'// To loop and work with each textbox
For Each tb In tbList
'// do something with tb
Next
Yes, you can use a collection if you want to go to the trouble. But an even easier way to work with it is to use VB6's (now obsolete) control array implementation. Most of us were disappointed when we found it was no longer available in .Net!
All you have to do to get control arrays in VB6 is create a bunch of controls with the same name. (They do have to be the same type of control; you can't make arrays of, say, text boxes and combo boxes.) Start with one text box, name it what you want, and copy it. You will be asked if you want to create a control array. Say yes, copy as many as you want. You will notice that the Index property is no longer blank, starting with 0 and incrementing from there. You will also notice that all of the event handlers have an "Index As Integer" argument to them. This way, you can use the same event handler for all of them, evaluating the Index argument to find out which member of your array is firing the event.
Here is the old doc for it. Microsoft makes it hard to find. :)

How to Create a new blank Record without it showing the previous data from the previous record?

I keep trying to create a new record expecting a new blank form to show up but all of the info from the previous form shows up.
I need a blank form to enter data but i also need to save all of my previous forms. Overall, I want to keep adding onto my records. But somehow it won't let me.
If I'm understanding correctly you open a form but it is showing the data you previously entered? if it is that you want avoid seeing the previous data and just have a blank for for entry that you should set the form properties Data Entry to Yes
Depending on whether or not you have a bound form, Kefash' answer could work.
If you have an unbound form however, it will not.
If you do have an unbound form, the simplest way is to just loop through all the controls and empty them.
For example:
Private Sub save_Click()
Dim ctl As Control
For Each ctl In Me.Controls
Select Case ctl.ControlType
Case acListBox
If Len(ctl.ControlSource) = 0 Then
ctl.Value = Null
End If
Case acCheckBox
ctl.Value = 0
Case acTextBox
ctl.value = ""
End Select
Next
End Sub

Resources