How to add autofill to all the textboxes - excel

I am new to vba and I want to learn. Please help me with the following:
I have this application(see the picture) which I use to enter data in a worksheet (it is more easy to use compared to completing manually). It is basically an inventory management system. When I add a product in there I can choose between sale or purchase.
The next thing I would like to implement is to autofill the form whenever I want to add a sale (considering it was added as a purchase in the first place), based on a serial code for example. This would be very useful because I wont have to complete all the text boxes again when I enter a sale in the database
Do you have any ideas about how I could do this?
Kind regard,
Traian.

So, basically I shouldn't help since you have not done your research, but I did find it interesting to see if I could create such a function.
You wont be able to simply paste the code but it does work exactly as a autofill.
This is the "data" source I used for the autofill, it's looking for the left value and will autocomplete that textbox, as well as a secondary textbox with the value from column C. This would work with n numbers of autofills.
I only used 2 different fields to test this idea, disregard the labels. This is how it looked without typing anything.
As soon as you start to type, the "autofill" appears.
If you were to "hover" over the autofill, it will turn a different color, as well as all the input sheets, the input sheet also now includes the autofilled answers. if you were to "unhover"(hover over anything except the autofill) it will revert back to the second picture.
If I were to write this code again for a real project, I would change a couple of thing.
There might be leftover code from my testing, I would remove this.
I would use global variables so to avoid declaring variables more than one time.
I would name the textboxes and label in a better way.
I would complicate the textboxes with labels as to get the text to align in center.
The order of the code might not be the best for you to understand.
etc.
Here is the code:
Private Sub Autofill_Click()
Dim BestOption As Integer
Dim ValueRange As Range
Set ValueRange = Sheets("sheet1").Range("B8:B13")
Dim Start As Range
Set Start = Sheets("sheet1").Range("B7")
BestOption = WorksheetFunction.Match(Autofill, ValueRange, 0)
TextBox2 = Start.Offset(BestOption, 1)
TextBox1 = Start.Offset(BestOption, 0)
Autofill.Visible = False
TextBox3.Visible = False
TextBox4.Visible = False
End Sub
Private Sub TextBox1_Change()
Dim Start As Range
Dim ValueRange As Range
Dim MatchCounter As Integer
Set Start = Sheets("sheet1").Range("B7")
Set ValueRange = Sheets("sheet1").Range("B8:B13")
If TextBox1 = "" Then
Autofill.Visible = False
Else
'Call FindClosestMatch(TextBox1)
Autofill.Visible = True
Autofill = Start.Offset(FindClosestMatch(TextBox1) + 1, 0)
End If
End Sub
Function FindClosestMatch(Entry As String) As Integer
Dim BestOption As Integer
Dim Start As Range
Set Start = Sheets("sheet1").Range("B7")
Dim MyArray(6) As String
Dim i As Integer
Dim j As Integer
Dim iChar As String
Dim EntryChar As String
For i = 0 To 5
MyArray(i) = Start.Offset(i + 1, 0)
Next i
For j = 1 To Len(Entry)
EntryChar = Mid(Entry, j, 1)
For i = 0 To 5
If EntryChar = "" Then
Exit For
End If
iChar = Mid(MyArray(i), j, 1)
If iChar = EntryChar Then
BestOption = i
Else
MyArray(i) = "................."
End If
Next i
Next j
FindClosestMatch = BestOption
End Function
'hover
Private Sub Autofill_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Autofill.BackColor = &H80000002
TextBox3.BackColor = &H80000002
TextBox4.BackColor = &H80000002
Dim BestOption As Integer
Dim ValueRange As Range
Set ValueRange = Sheets("sheet1").Range("B8:B13")
Dim Start As Range
Set Start = Sheets("sheet1").Range("B7")
BestOption = WorksheetFunction.Match(Autofill, ValueRange, 0)
TextBox3.Visible = True
TextBox4.Visible = True
TextBox4 = Start.Offset(BestOption, 1)
TextBox3 = Start.Offset(BestOption, 0)
End Sub
Private Sub TextBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Call test
End Sub
Private Sub TextBox2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Call test
End Sub
Private Sub TextBox3_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Call test
End Sub
Private Sub TextBox4_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Call test
End Sub
Private Sub UserForm_Click()
Call test
Autofill.Visible = False
End Sub
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Call test
End Sub
Sub test()
Autofill.BackColor = &H80000000
TextBox1.BackColor = &H80000005
TextBox2.BackColor = &H80000005
TextBox3.Visible = False
TextBox4.Visible = False
End Sub
Problem to think about:
The autofill always give the best answer, even if no good answer exist. In those cases, the best answer is the first answer in the data structure.
It is case sensitive.
One charachter wrong and you wont find your answer.
Notes:
I used 4 textboxes, number 1 and 2 are sitting on top of each other, and number 2 and 4 are on top of each other. This was done to not lose the already typed input if you accidently hovered over the autofill.

Related

Excel VBA hide several columns in 1 line of code

I want to hide several columns (that's not close to each other) in 1 line (shown below) in VBA but it doesn't work. What's wrong with it?
Columns("A, C:D").hidden = True
Use Range.EntireColumn.
Range("A:A,C:D").EntireColumn.Hidden = True
This thread is similar, and this answer demonstrates that Union is another option here as well.
Note that .EntireColumn is necessary; omitting it will throw a
Run-time error '1004':
Unable to set the Hidden property of the Range class.
Hide Columns Using Union
The best answer is already posted so here is kind of a cheating one: it is one line, but uses a function (with 'several' lines).
Option Explicit
Sub hideColumns()
CombinedColumns(ActiveSheet, "A,C,H,K:M,O,R:U").Hidden = True
End Sub
Function CombinedColumns( _
ByVal ws As Worksheet, _
ByVal ColumnsList As String, _
Optional ByVal Delimiter As String = ",") _
As Range
Dim Cols() As String: Cols = Split(ColumnsList, Delimiter)
Dim rg As Range
Dim n As Long
For n = 0 To UBound(Cols)
If rg Is Nothing Then
Set rg = ws.Columns(Cols(n))
Else
Set rg = Union(rg, ws.Columns(Cols(n)))
End If
Next n
If Not rg Is Nothing Then
Set CombinedColumns = rg.EntireColumn
End If
End Function

Excel and VBA Gives Different Answers

I have faced a strange issue(Maybe a bug) at Data Validation
When i use below code for data validation, Excel is creating a dropdown but add string as one line. But when i write the same string from Excel data validation menu, Excel is seperating the dropdown with multiple items which i want.
For example, lets say string is "A;B;C"
When i do it by VBA, dropdown shows "A;B;C" as 1 line but when i click data validation menu and write manually "A;B;C" , Excel is creating 3 lines of dropdown with "A" , "B" , "C"
It is totally strange behavior. You may see the code as below. I add video link to explain better.
https://streamable.com/a75kud
Public arrAddress As String
Sub DynamicDataVal()
Dim rng As Range
Dim cll As Range
Dim dValicationCount As Long
Dim un As String
Dim DValidationList As Range
Dim DValidationListString As String
Dim seper As String
Dim col As New Collection, a
Dim colIt As Variant
Dim arr() As Variant
un = "Sayin " & Environ("UserName")
On Error Resume Next
Set rng = Application.InputBox("Lutfen Veri Alanini Seciniz", un, ActiveCell.Address, , , , , 8)
If rng Is Nothing Then Exit Sub
Set cll = ActiveCell
dValicationCount = cll.SpecialCells(xlCellTypeSameValidation).Count
If dValicationCount = 0 Then
arr = rng.Offset(1, 0).Resize(rng.Resize(, 1).Cells.Count - 1, 1).Value
arrAddress = rng.Address(External:=True)
For Each a In arr
col.Add a, a
Next a
seper = ListSeperatorMod.GetListSeparator
For Each colIt In col
DValidationListString = DValidationListString & seper & colIt
Next colIt
DValidationListString = Right(DValidationListString, Len(DValidationListString) - 1)
On Error GoTo 0
With cll.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:=DValidationListString
End With
Else
If rng.Validation.Type <> 3 Then
Exit Sub
Else
'Will be done
End If
End If
On Error GoTo 0
End Sub
Private Declare Function GetLocaleInfo Lib "kernel32" Alias "GetLocaleInfoA" _
(ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData As Long) As Long
Private Declare Function GetUserDefaultLCID% Lib "kernel32" ()
Private Const LOCALE_SLIST = &HC
Public Function GetListSeparator() As String
Dim ListSeparator As String
Dim iRetVal1 As Long
Dim iRetVal2 As Long
Dim lpLCDataVar As String
Dim Position As Integer
Dim Locale As Long
Locale = GetUserDefaultLCID()
iRetVal1 = GetLocaleInfo(Locale, LOCALE_SLIST, lpLCDataVar, 0)
ListSeparator = String$(iRetVal1, 0)
iRetVal2 = GetLocaleInfo(Locale, LOCALE_SLIST, ListSeparator, iRetVal1)
Position = InStr(ListSeparator, Chr$(0))
If Position > 0 Then
ListSeparator = Left$(ListSeparator, Position - 1)
End If
GetListSeparator = ListSeparator
End Function
It's by design and it's the the same behavior you'd see if using VBA to enter a formula in a cell: you always use the "US" list separator , in VBA, and not (eg) the locale-specific separator such as ;.
This differs from entering a formula via the user interface, where you're always using the locale-specific separator.
So in VBA you might use:
Range("A1").Formula = "=MAX(A1, B2)"
and if your locale separator is ; then the formula shows up on the sheet as:
=MAX(A1; B2)
This alows the same VBA to function across different locales without modifications

Drag and drop multiple files to Userform

I am trying to drag and drop more than one file on a Userform to get their paths. I managed it with one file thanks to this.
When I change FilePath = Data.files(1) to FilePath = Data.Files(2) or (i), I get a message "Table Expected". Should I create a Table and Redim it?
My work so far:
This one does the job (opening the file and copying it into a selected sheet)
Dim Wb, FilePath As String
Dim WbIni, WbCib As Workbook
Private Sub CommandButton2_Click()
If FilePath = vbNullString Then
MsgBox "Aucun fichier n'a été importé", vbCritical, "Anomalie"
Unload UserForm1
Exit Sub
End If
Set WbCib = Workbooks.Open(Filename:=FilePath)
MsgBox WbCib.Name
i = WbCib.ActiveSheet.Range("A1").End(xlDown).Row
WbCib.ActiveSheet.Range("A1:A" & i).Copy
ActiveSheet.Paste Destination:=WbIni.Worksheets("Target").Range("A1:A" & i)
WbIni.Sheets("Target").Activate
WbCib.Close
Unload UserForm1
End Sub
This one initializes my TreeView for the drag and drop
Private Sub UserForm_Initialize()
Wb = ThisWorkbook.Name
Set WbIni = ActiveWorkbook
TreeView1.OLEDropMode = ccOLEDropManual
End Sub
This one gives me the file path. I think I need to loop it.
Private Sub TreeView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
FilePath = Data.Files(1)
Workbooks(Wb).Activate
MsgBox FilePath
End Sub
Thanks to #R.Roe's comment, I managed to do what I wanted :
Dim x, y As Integer
Dim PathTable As String
Dim FilePath As Variant
Private Sub TreeView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
Dim i As Integer
Workbooks(Wb).Activate
'Counting file paths I dropped
For Each FilePath In Data.Files()
i = i + 1
Next FilePath
'Redim my table
ReDim PathTable(i)
i = 1
'Adding data to my table
For x = 0 To UBound(PathTable) - 1
PathTable(x) = Data.Files(i)
i = i + 1
Next x
'Just to make sure it works
For x = 0 To UBound(PathTable) - 1
MsgBox PathTable(x)
Next x
End Sub
Cheers!

Formatting MM/DD/YYYY dates in textbox in VBA

I'm looking for a way to automatically format the date in a VBA text box to a MM/DD/YYYY format, and I want it to format as the user is typing it in. For instance, once the user types in the second number, the program will automatically type in a "/". Now, I got this working (as well as the second dash) with the following code:
Private Sub txtBoxBDayHim_Change()
If txtBoxBDayHim.TextLength = 2 or txtBoxBDayHim.TextLength = 5 then
txtBoxBDayHim.Text = txtBoxBDayHim.Text + "/"
End Sub
Now, this works great when typing. However, when trying to delete, it still enters in the dashes, so its impossible for the user to delete past one of the dashes (deleting a dash results in a length of 2 or 5, and the sub is then run again, adding in another dash). Any suggestions on a better way to do this?
I never suggest using Textboxes or Inputboxes to accept dates. So many things can go wrong. I cannot even suggest using the Calendar Control or the Date Picker as for that you need to register the mscal.ocx or mscomct2.ocx and that is very painful as they are not freely distributable files.
Here is what I recommend. You can use this custom made calendar to accept dates from the user
PROS:
You don't have to worry about user inputting wrong info
You don't have to worry user pasting in the textbox
You don't have to worry about writing any major code
Attractive GUI
Can be easily incorporated in your application
Doesn't use any controls for which you need to reference any libraries like mscal.ocx or mscomct2.ocx
CONS:
Ummm...Ummm... Can't think of any...
HOW TO USE IT (File missing from my dropbox. Please refer to the bottom of the post for an upgraded version of the calendar)
Download the Userform1.frm and Userform1.frx from here.
In your VBA, simply import Userform1.frm as shown in the image below.
Importing the form
RUNNING IT
You can call it in any procedure. For example
Sub Sample()
UserForm1.Show
End Sub
SCREEN SHOTS IN ACTION
NOTE: You may also want to see Taking Calendar to new level
This is the same concept as Siddharth Rout's answer. But I wanted a date picker which could be fully customized so that the look and feel could be tailored to whatever project it's being used in.
You can click this link to download the custom date picker I came up with. Below are some screenshots of the form in action.
To use the date picker, simply import the CalendarForm.frm file into your VBA project. Each of the calendars above can be obtained with one single function call. The result just depends on the arguments you use (all of which are optional), so you can customize it as much or as little as you want.
For example, the most basic calendar on the left can be obtained by the following line of code:
MyDateVariable = CalendarForm.GetDate
That's all there is to it. From there, you just include whichever arguments you want to get the calendar you want. The function call below will generate the green calendar on the right:
MyDateVariable = CalendarForm.GetDate( _
SelectedDate:=Date, _
DateFontSize:=11, _
TodayButton:=True, _
BackgroundColor:=RGB(242, 248, 238), _
HeaderColor:=RGB(84, 130, 53), _
HeaderFontColor:=RGB(255, 255, 255), _
SubHeaderColor:=RGB(226, 239, 218), _
SubHeaderFontColor:=RGB(55, 86, 35), _
DateColor:=RGB(242, 248, 238), _
DateFontColor:=RGB(55, 86, 35), _
SaturdayFontColor:=RGB(55, 86, 35), _
SundayFontColor:=RGB(55, 86, 35), _
TrailingMonthFontColor:=RGB(106, 163, 67), _
DateHoverColor:=RGB(198, 224, 180), _
DateSelectedColor:=RGB(169, 208, 142), _
TodayFontColor:=RGB(255, 0, 0), _
DateSpecialEffect:=fmSpecialEffectRaised)
Here is a small taste of some of the features it includes. All options are fully documented in the userform module itself:
Ease of use. The userform is completely self-contained, and can be imported into any VBA project and used without much, if any additional coding.
Simple, attractive design.
Fully customizable functionality, size, and color scheme
Limit user selection to a specific date range
Choose any day for the first day of the week
Include week numbers, and support for ISO standard
Clicking the month or year label in the header reveals selectable comboboxes
Dates change color when you mouse over them
Add something to track the length and allow you to do "checks" on whether the user is adding or subtracting text. This is currently untested but something similar to this should work (especially if you have a userform).
'add this to your userform or make it a static variable if it is not part of a userform
private oldLength as integer
Private Sub txtBoxBDayHim_Change()
if ( oldlength > txboxbdayhim.textlength ) then
oldlength =txtBoxBDayHim.textlength
exit sub
end if
If txtBoxBDayHim.TextLength = 2 or txtBoxBDayHim.TextLength = 5 then
txtBoxBDayHim.Text = txtBoxBDayHim.Text + "/"
end if
oldlength =txtBoxBDayHim.textlength
End Sub
I too, one way or another stumbled on the same dilemma, why the heck Excel VBA doesn't have a Date Picker. Thanks to Sid, who made an awesome job to create something for all of us.
Nonetheless, I came to a point where I need to create my own. And I am posting it here since a lot of people I'm sure lands on this post and benefit from it.
What I did was very simple as what Sid does except that I do not use a temporary worksheet. I thought the calculations are very simple and straight forward so there's no need to dump it somewhere else. Here's the final output of the calendar:
How to set it up:
Create 42 Label controls and name it sequentially and arranged left to right, top to bottom (This labels contains greyed 25 up to greyed 5 above). Change the name of the Label controls to Label_01,Label_02 and so on. Set all 42 labels Tag property to dts.
Create 7 more Label controls for the header (this will contain Su,Mo,Tu...)
Create 2 more Label control, one for the horizontal line (height set to 1) and one for the Month and Year display. Name the Label used for displaying month and year Label_MthYr
Insert 2 Image controls, one to contain the left icon to scroll previous months and one to scroll next month (I prefer simple left and right arrow head icon). Name it Image_Left and Image_Right
The layout should be more or less like this (I leave the creativity to anyone who'll use this).
Declaration: We need one variable declared at the very top to hold the current month selected.
Option Explicit
Private curMonth As Date
Private Procedure and Functions:
Private Function FirstCalSun(ref_date As Date) As Date
'/* returns the first Calendar sunday */
FirstCalSun = DateSerial(Year(ref_date), _
Month(ref_date), 1) - (Weekday(ref_date) - 1)
End Function
Private Sub Build_Calendar(first_sunday As Date)
'/* This builds the calendar and adds formatting to it */
Dim lDate As MSForms.Label
Dim i As Integer, a_date As Date
For i = 1 To 42
a_date = first_sunday + (i - 1)
Set lDate = Me.Controls("Label_" & Format(i, "00"))
lDate.Caption = Day(a_date)
If Month(a_date) <> Month(curMonth) Then
lDate.ForeColor = &H80000011
Else
If Weekday(a_date) = 1 Then
lDate.ForeColor = &HC0&
Else
lDate.ForeColor = &H80000012
End If
End If
Next
End Sub
Private Sub select_label(msForm_C As MSForms.Control)
'/* Capture the selected date */
Dim i As Integer, sel_date As Date
i = Split(msForm_C.Name, "_")(1) - 1
sel_date = FirstCalSun(curMonth) + i
'/* Transfer the date where you want it to go */
MsgBox sel_date
End Sub
Image Events:
Private Sub Image_Left_Click()
If Month(curMonth) = 1 Then
curMonth = DateSerial(Year(curMonth) - 1, 12, 1)
Else
curMonth = DateSerial(Year(curMonth), Month(curMonth) - 1, 1)
End If
With Me
.Label_MthYr.Caption = Format(curMonth, "mmmm, yyyy")
Build_Calendar FirstCalSun(curMonth)
End With
End Sub
Private Sub Image_Right_Click()
If Month(curMonth) = 12 Then
curMonth = DateSerial(Year(curMonth) + 1, 1, 1)
Else
curMonth = DateSerial(Year(curMonth), Month(curMonth) + 1, 1)
End If
With Me
.Label_MthYr.Caption = Format(curMonth, "mmmm, yyyy")
Build_Calendar FirstCalSun(curMonth)
End With
End Sub
I added this to make it look like the user is clicking the label and should be done on the Image_Right control too.
Private Sub Image_Left_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Me.Image_Left.BorderStyle = fmBorderStyleSingle
End Sub
Private Sub Image_Left_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Me.Image_Left.BorderStyle = fmBorderStyleNone
End Sub
Label Events: All of this should be done for all 42 labels (Label_01 to Lable_42) Tip: Build the first 10 and just use find and replace for the remaining.
Private Sub Label_01_Click()
select_label Me.Label_01
End Sub
This is for hovering over dates and clicking effect.
Private Sub Label_01_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Me.Label_01.BorderStyle = fmBorderStyleSingle
End Sub
Private Sub Label_01_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Me.Label_01.BackColor = &H8000000B
End Sub
Private Sub Label_01_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Me.Label_01.BorderStyle = fmBorderStyleNone
End Sub
UserForm Events:
Private Sub UserForm_Initialize()
'/* This is to initialize everything */
With Me
curMonth = DateSerial(Year(Date), Month(Date), 1)
.Label_MthYr = Format(curMonth, "mmmm, yyyy")
Build_Calendar FirstCalSun(curMonth)
End With
End Sub
Again, just for the hovering over dates effect.
Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
With Me
Dim ctl As MSForms.Control, lb As MSForms.Label
For Each ctl In .Controls
If ctl.Tag = "dts" Then
Set lb = ctl: lb.BackColor = &H80000005
End If
Next
End With
End Sub
And that's it. This is raw and you can add your own twist to it.
I've been using this for awhile and I have no issues (performance and functionality wise). No Error Handling yet but can be easily managed I guess. Actually, without the effects, the code is too short. You can manage where your dates go in the select_label procedure. HTH.
Just for fun I took Siddharth's suggestion of separate textboxes and did comboboxes. If anybody's interested, add a userform with three comboboxes named cboDay, cboMonth and cboYear and arrange them left to right. Then paste the code below into the UserForm's code module. The required combobox properties are set in UserFormInitialization, so no additional prep should be required.
The tricky part is changing the day when it becomes invalid because of a change in year or month. This code just resets it to 01 when that happens and highlights cboDay.
I haven't coded anything like this in a while. Hopefully it will be of interest to somebody, someday. If not it was fun!
Dim Initializing As Boolean
Private Sub UserForm_Initialize()
Dim i As Long
Dim ctl As MSForms.Control
Dim cbo As MSForms.ComboBox
Initializing = True
With Me
With .cboMonth
' .AddItem "month"
For i = 1 To 12
.AddItem Format(i, "00")
Next i
.Tag = "DateControl"
End With
With .cboDay
' .AddItem "day"
For i = 1 To 31
.AddItem Format(i, "00")
Next i
.Tag = "DateControl"
End With
With .cboYear
' .AddItem "year"
For i = Year(Now()) To Year(Now()) + 12
.AddItem i
Next i
.Tag = "DateControl"
End With
DoEvents
For Each ctl In Me.Controls
If ctl.Tag = "DateControl" Then
Set cbo = ctl
With cbo
.ListIndex = 0
.MatchRequired = True
.MatchEntry = fmMatchEntryComplete
.Style = fmStyleDropDownList
End With
End If
Next ctl
End With
Initializing = False
End Sub
Private Sub cboDay_Change()
If Not Initializing Then
If Not IsValidDate Then
ResetMonth
End If
End If
End Sub
Private Sub cboMonth_Change()
If Not Initializing Then
ResetDayList
If Not IsValidDate Then
ResetMonth
End If
End If
End Sub
Private Sub cboYear_Change()
If Not Initializing Then
ResetDayList
If Not IsValidDate Then
ResetMonth
End If
End If
End Sub
Function IsValidDate() As Boolean
With Me
IsValidDate = IsDate(.cboMonth & "/" & .cboDay & "/" & .cboYear)
End With
End Function
Sub ResetDayList()
Dim i As Long
Dim StartDay As String
With Me.cboDay
StartDay = .Text
For i = 31 To 29 Step -1
On Error Resume Next
.RemoveItem i - 1
On Error GoTo 0
Next i
For i = 29 To 31
If IsDate(Me.cboMonth & "/" & i & "/" & Me.cboYear) Then
.AddItem Format(i, "0")
End If
Next i
On Error Resume Next
.Text = StartDay
If Err.Number <> 0 Then
.SetFocus
.ListIndex = 0
End If
End With
End Sub
Sub ResetMonth()
Me.cboDay.ListIndex = 0
End Sub
For a quick solution, I usually do like this.
This approach will allow the user to enter date in any format they like in the textbox, and finally format in mm/dd/yyyy format when he is done editing. So it is quite flexible:
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If TextBox1.Text <> "" Then
If IsDate(TextBox1.Text) Then
TextBox1.Text = Format(TextBox1.Text, "mm/dd/yyyy")
Else
MsgBox "Please enter a valid date!"
Cancel = True
End If
End If
End Sub
However, I think what Sid developed is a much better approach - a full fledged date picker control.
You could use an input mask on the text box, too. If you set the mask to ##/##/#### it will always be formatted as you type and you don't need to do any coding other than checking to see if what was entered was a true date.
Which just a few easy lines
txtUserName.SetFocus
If IsDate(txtUserName.text) Then
Debug.Print Format(CDate(txtUserName.text), "MM/DD/YYYY")
Else
Debug.Print "Not a real date"
End If
While I agree with what's mentioned in the answers below, suggesting that this is a very bad design for a Userform unless copious amounts of error checks are included...
to accomplish what you need to do, with minimal changes to your code, there are two approaches.
Use KeyUp() event instead of Change event for the textbox. Here is an example:
Private Sub TextBox2_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Dim TextStr As String
TextStr = TextBox2.Text
If KeyCode <> 8 Then ' i.e. not a backspace
If (Len(TextStr) = 2 Or Len(TextStr) = 5) Then
TextStr = TextStr & "/"
End If
End If
TextBox2.Text = TextStr
End Sub
Alternately, if you need to use the Change() event, use the following code. This alters the behavior so the user keeps entering the numbers, as
12072003
while the result as he's typing appears as
12/07/2003
But the '/' character appears only once the first character of the DD i.e. 0 of 07 is entered. Not ideal, but will still handle backspaces.
Private Sub TextBox1_Change()
Dim TextStr As String
TextStr = TextBox1.Text
If (Len(TextStr) = 3 And Mid(TextStr, 3, 1) <> "/") Then
TextStr = Left(TextStr, 2) & "/" & Right(TextStr, 1)
ElseIf (Len(TextStr) = 6 And Mid(TextStr, 6, 1) <> "/") Then
TextStr = Left(TextStr, 5) & "/" & Right(TextStr, 1)
End If
TextBox1.Text = TextStr
End Sub
Private Sub txtBoxBDayHim_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii >= 48 And KeyAscii <= 57 Or KeyAscii = 8 Then 'only numbers and backspace
If KeyAscii = 8 Then 'if backspace, ignores + "/"
Else
If txtBoxBDayHim.TextLength = 10 Then 'limit textbox to 10 characters
KeyAscii = 0
Else
If txtBoxBDayHim.TextLength = 2 Or txtBoxBDayHim.TextLength = 5 Then 'adds / automatically
txtBoxBDayHim.Text = txtBoxBDayHim.Text + "/"
End If
End If
End If
Else
KeyAscii = 0
End If
End Sub
This works for me. :)
Your code helped me a lot. Thanks!
I'm brazilian and my english is poor, sorry for any mistake.

Endless VBA Loop UNLESS I step through the code

I have a userform with 6 list objects. All of the list objects have named range rowsources. Clicking any one item in any one list will reference a chart on a spreadsheet and clear contents of any item's cell that does not belong with what was selected (explained better at the bottom of this, if you're interested). All of my list objects only have "After Update" triggers, everything else is handled by private subs.
Anyway, there's a lot of looping and jumping from list to list. If I run the userform normally, it endlessly loops. It seems to run through once, and then acts as though the user has again clicked the same item in the list, over and over again.
The odd thing is, if I step through the code (F8), it ends perfectly, when it's supposed to and control is returned to the user.
Does anyone have any thoughts on why that might be?
EDIT: I didn't originally post the code because all of it is basically a loop, and there's 150+ lines of it. I don't understand how it can be the code if stepping through makes it work perfectly, but allowing it to run regular makes it endless loop. Anyway, here's the code:
Option Explicit
Dim arySelected(6) As String
Dim intHoldCol As Integer, intHoldRow As Integer
Dim strHold As String
Dim rngStyleFind As Range, rngStyleList As Range
Private Sub UserForm_Activate()
Set rngStyleList = Range("Lists_W_Style")
Set rngStyleFind = Range("CABI_FindStyle")
End Sub
Private Sub lstStyle_AfterUpdate()
If lstStyle.ListIndex >= 0 Then
arySelected(0) = lstStyle.Value
Call FilterCabinetOptions(Range("Lists_W_Style"), Range("CABI_FindStyle"), 0)
End If
End Sub
Private Sub lstWood_AfterUpdate()
If lstWood.ListIndex >= 0 Then
arySelected(1) = lstWood.Value
Call FilterCabinetOptions(Range("Lists_W_Wood"), Range("CABI_FindWood"), 1)
' lstWood.RowSource = "Lists_W_Wood"
End If
End Sub
Private Sub cmdReset_Click()
Range("Lists_S_Style").Copy Destination:=Range("Lists_W_Style")
Call RemoveXes(Range("Lists_W_Style"))
Range("Lists_S_Wood").Copy Destination:=Range("Lists_W_Wood")
Call RemoveXes(Range("Lists_W_Wood"))
Range("Lists_S_Door").Copy Destination:=Range("Lists_W_Door")
Call RemoveXes(Range("Lists_W_Door"))
Range("Lists_S_Color").Copy Destination:=Range("Lists_W_Color")
Call RemoveXes(Range("Lists_W_Color"))
Range("Lists_S_Glaze").Copy Destination:=Range("Lists_W_Glaze")
Call RemoveXes(Range("Lists_W_Glaze"))
Range("Lists_S_Const").Copy Destination:=Range("Lists_W_Const")
Call RemoveXes(Range("Lists_W_Const"))
Range("Lists_S_DrawFrontConst").Copy Destination:=Range("Lists_W_DrawFrontConst")
Call RemoveXes(Range("Lists_W_DrawFrontConst"))
End Sub
Private Sub FilterCabinetOptions(rngList As Range, rngFind As Range, intAry As Integer)
Dim intListCntr As Integer, intFindCntr As Integer, intStyleCntr As Integer
If intAry = 0 Then
Call FindStyle(arySelected(intAry))
Else
'Save the List item.
For intListCntr = 1 To rngList.Rows.Count
If rngList.Cells(intListCntr, 1) = arySelected(intAry) Then
rngList.Cells(intListCntr, 3) = "X"
' Call RemoveNonXes(rngList)
Exit For
End If
Next intListCntr
'Save the column of the Find List.
For intFindCntr = 1 To rngFind.Columns.Count
If rngFind.Cells(1, intFindCntr) = arySelected(intAry) Then
'Minus 2 to allow for columns A and B when using Offset in the below loop.
intHoldCol = rngFind.Cells(1, intFindCntr).Column - 2
Exit For
End If
Next intFindCntr
'Find appliciple styles.
For intStyleCntr = 1 To rngStyleFind.Rows.Count
If Len(rngStyleFind.Cells(intStyleCntr, intHoldCol)) > 0 Then
Call FindStyle(rngStyleFind.Cells(intStyleCntr, 1))
End If
Next intStyleCntr
End If
Call RemoveNonXes(rngStyleList)
Call RemoveNonXes(Range("Lists_W_Wood"))
Call RemoveNonXes(Range("Lists_W_Door"))
Call RemoveNonXes(Range("Lists_W_Color"))
Call RemoveNonXes(Range("Lists_W_Glaze"))
Call RemoveNonXes(Range("Lists_W_Const"))
Call RemoveNonXes(Range("Lists_W_DrawFrontConst"))
End Sub
Private Sub FindStyle(strFindCode As String)
Dim intListCntr As Integer, intFindCntr As Integer
For intListCntr = 1 To rngStyleList.Rows.Count
If rngStyleList.Cells(intListCntr, 1) = strFindCode Then
rngStyleList.Range("C" & intListCntr) = "X"
Exit For
End If
Next intListCntr
For intFindCntr = 1 To rngStyleFind.Rows.Count
If rngStyleFind.Cells(intFindCntr, 1) = strFindCode Then
intHoldRow = rngStyleFind.Cells(intFindCntr).Row
Exit For
End If
Next intFindCntr
If Len(arySelected(1)) = 0 Then Call FindStyleOptions(Range("CABI_FindWood"), Range("Lists_W_Wood"))
If Len(arySelected(2)) = 0 Then Call FindStyleOptions(Range("CABI_FindDoor"), Range("Lists_W_Door"))
If Len(arySelected(3)) = 0 Then Call FindStyleOptions(Range("CABI_FindColor"), Range("Lists_W_Color"), Range("Lists_W_Wood"))
If Len(arySelected(4)) = 0 Then Call FindStyleOptions(Range("CABI_FindGlaze"), Range("Lists_W_Glaze"), Range("Lists_W_Wood"))
If Len(arySelected(5)) = 0 Then Call FindStyleOptions(Range("CABI_FindConst"), Range("Lists_W_Const"))
If Len(arySelected(6)) = 0 Then Call FindStyleOptions(Range("CABI_FindDrawFrontConst"), Range("Lists_W_DrawFrontConst"))
End Sub
Private Sub FindStyleOptions(rngFind As Range, rngList As Range, Optional rngCheckList As Range)
Dim intListCntr As Integer, intFindCntr As Integer
Dim intStrFinder As Integer, intCheckCntr As Integer
Dim strHoldCheck As String
Dim strHoldFound As String, strHoldOption As String
'Go through the appropriate find list (across the top of CABI)
For intFindCntr = 1 To rngFind.Columns.Count
strHoldOption = rngFind.Cells(1, intFindCntr)
strHoldFound = rngFind.Cells(1, intFindCntr).Offset((intHoldRow - 1), 0)
If Len(strHoldFound) > 0 Then
If rngCheckList Is Nothing Then
For intListCntr = 1 To rngList.Rows.Count
If rngList.Cells(intListCntr, 1) = strHoldFound Then
Call AddXes(rngList, strHoldFound, "X")
Exit For
End If
Next intListCntr
Else
intStrFinder = 1
Do While intStrFinder < Len(rngFind.Cells(1, intFindCntr).Offset((intHoldRow - 1), 0))
strHoldCheck = Mid(rngFind.Cells(1, intFindCntr).Offset((intHoldRow - 1), 0), intStrFinder, 2)
intStrFinder = intStrFinder + 3
For intCheckCntr = 1 To rngCheckList.Rows.Count
If strHoldCheck = rngCheckList(intCheckCntr, 1) And Len(rngCheckList(intCheckCntr, 3)) > 0 Then
Call AddXes(rngList, strHoldOption, "X")
intStrFinder = 99
Exit For
End If
Next intCheckCntr
Loop
End If
End If
Next intFindCntr
End Sub
Private Sub AddXes(rngList As Range, strToFind As String, strX As String)
Dim intXcntr As Integer
For intXcntr = 1 To rngList.Rows.Count
If rngList.Cells(intXcntr, 1) = strToFind Then
rngList.Cells(intXcntr, 3) = strX
Exit For
End If
Next intXcntr
End Sub
Private Sub RemoveNonXes(rngList As Range)
Dim intXcntr As Integer
For intXcntr = 1 To rngList.Rows.Count
If Len(rngList(intXcntr, 3)) = 0 Then
rngList.Range("A" & intXcntr & ":B" & intXcntr) = ""
Else
rngList.Range("C" & intXcntr) = ""
End If
Next intXcntr
End Sub
Private Sub RemoveXes(rngList As Range)
rngList.Range("C1:C" & rngList.Rows.Count) = ""
End Sub
Explanation:
Imagine you had 6 lists with different automobile conditions. So Make would be one list with Chevy, Ford, Honda... Model would be another with Malibu, Focus, Civic... But you'd also have Color Blue, Red, Green... So if your user wants a Green car, the program references an inventory list and gets rid of any Makes, Models, etc... not available in green. Likewise the user could click on Civic from the Model list and it would elminate all but Honda from the Make and so on. That's what I'm trying to do anyway.
Without seeing the code it's tough to tell. When you run the script, the 'AfterUpdate' event may be getting triggered over and over, causing the endless loop. Try using a counter to limit the update to one change and have it exit the loop once the counter is greater than 0.

Resources