I'm trying to build a file to compare the working time for every worker vs their scheduled time.
I've build some classes to achieve that, but I'm having problems when giving times as keys for the dictionary classes.
Here you can see I already have a key #2:30:00# (item 4) and I'm giving a key #2:30:00# but the code wants to add it as a new one:
Once I add it:
Now I have 2 items with the same value (item 4 and item 17).
This is the code for the class:
Option Explicit
Private m_Tramo As Object
Property Get Tramos(ByVal Key As Date) As Tramos
With m_Tramo
If Not .Exists(Key) Then .Add Key, New Tramos
End With
Set Tramos = m_Tramo(Key)
End Property
Private Sub Class_Initialize()
Set m_Tramo = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set m_Tramo = Nothing
End Sub
Public Property Get Keys() As Variant
Keys = m_Tramo.Keys
End Property
Public Property Get Count() As Long
Count = m_Tramo.Count
End Property
There is more to it, but it doesn't matter here.
Now the code to add the keys for the first time:
Sub CalculaTramosProgramados(arr As Variant, Agentes As Buca, Horario As String, AgenteHoy As Dias)
Dim HoraI As Date
HoraI = Left(Horario, 5)
Dim HoraF As Date
HoraF = Right(Horario, 5)
If HoraF < HoraI Then HoraF = HoraF + 1
Dim TramoInicial As Date
If Minute(HoraI) < 30 Then
TramoInicial = TimeSerial(Hour(HoraI), 0, 0)
Else
TramoInicial = TimeSerial(Hour(HoraI), 30, 0)
End If
'Vamos a rellenar con 30 minutos todos los tramos
Dim i As Long
Dim TramoActual As Date
For i = 0 To Application.RoundUp(DateDiff("n", HoraI, HoraF) / 30, 0) - 1
TramoActual = TramoInicial + TimeSerial(0, 30 * i, 0)
AgenteHoy.Tramos(TramoActual).Programado = 30
Next i
'Primer tramo no completo
If Minute(HoraI) <> 0 Or Minute(HoraI) <> 30 Then
If Minute(HoraI) < 30 Then
AgenteHoy.Tramos(TimeSerial(Hour(HoraI), 0, 0)).Programado = 30 - Minute(HoraI)
Else
AgenteHoy.Tramos(TimeSerial(Hour(HoraI), 30, 0)).Programado = 60 - Minute(HoraI)
End If
End If
'Ășltimo tramo no completo
If Minute(HoraF) <> 0 Or Minute(HoraF) <> 30 Then
If Minute(HoraF) < 30 Then
AgenteHoy.Tramos(TimeSerial(Hour(HoraF), 0, 0)).Programado = Minute(HoraF)
Else
AgenteHoy.Tramos(TimeSerial(Hour(HoraF), 30, 0)).Programado = Abs(30 - Minute(HoraF))
End If
End If
End Sub
The parameters are an array full of data, a initialized instance for the main class(which contains this one), schedule and shortcut reference for the parent class for this one (to clean a bit the code).
This procedure fills everything up allright, but when I come back here, then the code tries to add the new time when it already exists.
Sub CargarReales(arr As Variant, Agentes As Buca)
Dim i As Long
Dim Login As String
Dim Centro As String
Dim Dia As Date, Tramo As Date
Dim CargarTramo As Boolean
Dim AgenteHoy As Dias
For i = 2 To UBound(arr)
Dia = arr(i, 1)
Centro = arr(i, 2)
CargarTramo = compruebaTramo(Dia, Centro)
If Not CargarTramo Then GoTo Siguiente
Login = arr(i, 4)
Tramo = Mid(arr(i, 3), 4, 8)
Set AgenteHoy = Agentes.Logins(Login).Dias(Dia)
AgenteHoy.Tramos(Tramo).Real = arr(i, 5) / 60
Siguiente:
Next i
End Sub
Again, parameters are an array full of data and the main class.
I made sure that everything the code was providing to the class was the right type of variable, in this case Date variables holding only time, not dates.
Any clues on why is VBA doing this?
Related
I have a UserForm with a MultipageControl (name Controller_MultiPage).
At runtime my code adds pages to the Multipage and creates a newListView on each page.
Every ListView has:
With newListView
.MultiSelect = False
.Width = Controller_MultiPage.Width - 10
.Height = Controller_MultiPage.Height - 20
.View = lvwReport
.HideColumnHeaders = False
.ColumnHeaders.Add Text:="Signal Name", Width:=.Width / 10 * 4
.ColumnHeaders.Add Text:="Type", Width:=.Width / 10
.ColumnHeaders.Add Text:="I/O", Width:=.Width / 10
.ColumnHeaders.Add Text:="Description", Width:=.Width / 10 * 4
.CheckBoxes = True
.FullRowSelect = True
End With
then I populate the newListView with data from an XML file:
For Each node In list
With node.Attributes
Set listItem = newListView.ListItems.Add(Text:=.getNamedItem("Name").Text)
listItem.ListSubItems.Add = .getNamedItem("Type").Text
listItem.ListSubItems.Add = IIf(.getNamedItem("Input").Text = "1", "IN", "OUT")
listItem.ListSubItems.Add = .getNamedItem("Description").Text
listItem.Checked = False
End With
Next
but the checkboxes do not show. I can see the space for them in front of the first column and by clicking that space the checkbox of that particular row then appears. What I also noticed is that if I change the property
listItem.Checked = True
the behavior described above does not change, and when I click the free space in front of the first column (checkboxes space) the chsckbox that then shows up is still unchecked.
Any idea?
The problem seems to be in the behavior of the MultiPage control.
What I noticed was that if I forced the checkboxes' status (checked or unchecked) from the code, using the MultiPage_Change event, then the checkboxes show up.
So what I did was to create a class that holds the status of all checkboxes of all listviews on a single page, instantiate the Class for each ListView and store everything into a Dictionary, using the newListView.Name as Key
Then when the user changes page, the MultiPage_Change event that fires resets all the values of the checkboxes according to the Dictionary stored values.
In the Listview_N_ItemChecked event some other code updates the status of the item stored in the Dictionary.
Kind of cumbersome but it works.
the class (updated):
' Class Name = ComponentsSignalsRecord
Option Explicit
Dim Name As String
' NOTE: Signals(0) will always be empty and status(0) will always be False
Dim Signals() As String
Dim Status() As Boolean
Dim Component As String
Property Let SetComponentName(argName As String)
Component = argName
End Property
Property Get GetComponentName() As String
GetComponentName = Component
End Property
Property Get getSignalName(argIndex) As String
If argIndex >= LBound(Signals) And argIndex <= UBound(Signals) Then
getSignalName = Signals(argIndex)
Else
getSignalName = vbNullString
End If
End Property
Property Get dumpAll() As String()
dumpAll = Signals
End Property
Property Get Count() As Long
Count = UBound(Signals)
End Property
Property Get getStatus(argName As String) As Integer
' returns: -1 = Not Found; 1 = True; 0 = False
getStatus = -1
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then getStatus = IIf(Status(i) = True, 1, 0): Exit For
Next
End Property
Property Let setName(argName As String)
Name = argName
End Property
Property Get getName() As String
getName = Name
End Property
Public Sub UncheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = False
Next
End Sub
Public Sub CheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = True
Next
End Sub
Public Sub deleteSignal(argName As String)
Dim spoolSignals() As String
Dim spoolStatus() As Boolean
Dim i As Integer
spoolSignals = Signals
spoolStatus = Status
ReDim Signals(0)
ReDim Status(0)
For i = 1 To UBound(spoolSignals)
If argName <> spoolSignals(i) Then
ReDim Preserve Signals(UBound(Signals) + 1): Signals(UBound(Signals)) = spoolSignals(i)
ReDim Preserve Status(UBound(Status) + 1): Status(UBound(Status)) = spoolStatus(i)
End If
Next
End Sub
Public Sub addSignal(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then GoTo bye
Next
ReDim Preserve Signals(UBound(Signals) + 1)
ReDim Preserve Status(UBound(Status) + 1)
Signals(UBound(Signals)) = argName
Status(UBound(Status)) = argValue
bye:
End Sub
Public Sub setStatus(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then Status(i) = argValue: Exit For
Next
End Sub
Private Sub Class_Initialize()
ReDim Signals(0)
ReDim Status(0)
End Sub
The Form relevant code. Module level:
Dim myDict As New Dictionary ' the Dictionary
Dim ComponentsSignalsList As ComponentsSignalsRecord ' the Class
for each ListView created, may be one or more for every single MultiPage page :
Set ComponentsSignalsList = New ComponentsSignalsRecord
ComponentsSignalsList.setName = newListView.name
while populating the listview(s) in a loop for each single item added:
ComponentsSignalsList.addSignal List_Item.Text, List_Item.Checked
end of each loop, add the Class instance to the Dictionary:
myDict.Add ComponentsSignalsList.getName, ComponentsSignalsList
Now when changing Page in the MultiPage widget:
Private Sub Controller_MultiPage_Change()
If isLoading Then Exit Sub 'avoid errors and undue behavior while initializing the MultiPage widget
Dim locControl As Control
Dim controlType As String: controlType = "ListView"
With Controller_MultiPage
For Each locControl In .Pages(.value).Controls
If InStr(1, TypeName(locControl), controlType) > 0 Then
Call Check_CheckBoxes(locControl)
End If
Next
End With
End Sub
Private Sub Check_CheckBoxes(argListView As listView)
If argListView.CheckBoxes = False Then Exit Sub 'some ListViews don't have checkboxes
Dim myItem As ListItem
For Each myItem In argListView.ListItems
With myItem
.Checked = myDict.Item(argListView.name).getStatus(.Text)
End With
Next
End Sub
when ticking/unticking a checkbox (note the the ItemChecked event handler is defined in another Class Public WithEvents, where the handler calls this method passing both the ListView ID and the Item object) :
Public Sub ListViewsEvents_ItemCheck(argListView As listView, argItem As MSComctlLib.ListItem)
With argItem
myDict.Item((argListView .name).setStatus argName:=.Text, argValue:=.Checked
End With
End Sub
I just found the answer to the same problem that I also had and I feel so stupid. I had the first column of the Listview set to Width = 0... and thus the checkboxes would no longer show.
I gave it a width and everithing is back to normal...
I know there are endless posts for this but as my math skills are -100 I am getting issues in calculating the correct percentage. Below I have the loop that runs and then the sub that attempt to calculate the percentage. The issue is that the width of the label is wrong and for recordset with tiny numbers as 2 all is crewed up :-)
LOOP CODE
'loop until the end of the recordset
Do While Not Glob_RecSet.EOF
'inner loop to get each record fields
For FieldCount = 0 To Glob_RecSet.Fields.Count - 1
Glob_Sheet.Range(GLobWorkSheetRange).Offset(loopCounter, FieldCount).value = Glob_RecSet.Fields(FieldCount).value
Next
'start progress bar calculations the form show and unload is called on the form code apply changes button
RunProgressBar loopCounter, TotalRows, "Runningquery for " & Glob_RecSetRunning
'Next record
Glob_RecSet.MoveNext
'advance counter
loopCounter = loopCounter + 1
Loop
SUB CODE FOR PROGRESS BAR
Public Sub RunProgressBar(loopCounter As Variant, TotalRecords As Variant, FormTitle As String)
Dim LblDonewidth As Variant
Dim ProgBarCaption As Variant
Dim ProgresPercentage As Variant
If (TotalRecords < 100) Then
TotalRecords = 100
End If
ProgresPercentage = Round(loopCounter / TotalRecords * 100, 0)
'to avoid to give the progress bar a percentage greater than 100
If (ProgresPercentage > 100) Then
ProgresPercentage = 100
End If
ProgBarCaption = Round(ProgresPercentage, 0) & "%"
FrmProgBar.Caption = FormTitle
FrmProgBar.LblDone.Width = ProgresPercentage * 2
FrmProgBar.LblText.Caption = ProgBarCaption
'The DoEvents statement is responsible for the form updating
DoEvents
End Sub
I found the asnwer; the main issue was that I was not passing the corrent total of records in the recordset; this is solved by adding the line below before opening the recordset
'Clinet-Side cursor
Glob_RecSet.CursorLocation = adUseClient
then I found this example of progress bar here from which i took the correct logic for the progress bar percentage calculation.
Below the whole code
Sub InitProgressBar(maxValue As Long)
With FrmProgBar
.LblDone.Tag = .LblRemain.Width / maxValue
.LblDone.Width = 0
.LblText.Caption = ""
End With
End Sub
Public Sub RunProgressBar(loopCounter As Variant, formTitle As String)
Dim LblDonewidth As Variant
Dim ProgBarCaption As Variant
Dim ProgresPercentage As Variant
LblDonewidth = FrmProgBar.LblDone.Tag * loopCounter
ProgresPercentage = Round(FrmProgBar.LblDone.Width / FrmProgBar.LblRemain.Width * 100, 0)
ProgBarCaption = ProgresPercentage & "%"
'to avoid to give the progress bar a percentage greater than 100
If (ProgresPercentage > 100) Then
ProgresPercentage = 100
End If
FrmProgBar.Caption = formTitle
FrmProgBar.LblDone.Width = LblDonewidth
FrmProgBar.LblText.Caption = ProgBarCaption
End Sub
which is used as follow
TotalRecords = Glob_RecSet.RecordCount
'init progressbar
InitProgressBar (TotalRecords)
'loop until the end of the recordset
Do While Not Glob_RecSet.EOF
. . . .
'The DoEvents statement is responsible for the form updating
DoEvents
'start progress bar calculations the form show and unload
'is called on the form code apply changes button
RunProgressBar loopCounter, "Runningquery for " & Glob_RecSetRunning
I'm quite new to VBA (2 months in) and I'm trying to add three variables to a scripting dictionary in order to reformat an Excel Table and I am running into an error.
I have tried to add three variables by
countrydict.Add country, data, time
But I get an error message
Run-time error '450':
Wrong number of arguments or invalid property assignment
However it works if I write
countrydict.Add country, data 'or
countrydict.Add country, time
Dim lastrow As Long
Dim iter As Long
Dim diter As Long
Dim countrydict As Object
Dim country As String
Dim data As String
Dim time As String
Dim key As Variant
Dim i As Long
Const StartRow As Byte = 2
lastrow = Range("A" & StartRow).End(xlDown).Row
Set countrydict = CreateObject("Scripting.Dictionary")
Dim diter2 As Long, arr, arr2
With ActiveSheet
For iter = 2 To lastrow
country = Trim(.Cells(iter, 1).Value) '<<<<<
data = Trim(.Cells(iter, 2).Value) '<<<<<
time = Trim(.Cells(iter, 3).Text) '<<<<<
If countrydict.Exists(country) Then
If Not InStr(1, countrydict(country), data) > 0 Then
countrydict(country) = countrydict(country) & _
"|" & data & "/" & time
End If
Else
countrydict.Add country, data, time '<<<<<<<
End If
Next
iter = 2
For Each key In countrydict
.Cells(iter, 1).Value = key & ":"
.Cells(iter, 1).Font.Bold = True
.Cells(iter, 1).Font.ColorIndex = 30
iter = iter + 1
arr = Split(countrydict(key), "|")
For diter = 0 To UBound(arr)
arr2 = Split(arr(diter), "/")
.Cells(iter, 1).Value = arr2(0)
.Cells(iter, 2).Value = arr2(1)
Next diter
Next key
End With
End Sub
The expected result is to reformat a table in this format
"A" "B" "C"
EU Sales 10:00
EU Tax 12:00
USA Sales 09:00
USA Tax 10:00
Into this format
EU:
Sales 10:00
Tax 12:00
USA:
Sales 09:00
Tax 10:00
Many thanks for any help. I've been struggeling with this problem for days...
Another possibility is to create a new class to store your data. Store your data in an instance of this class and then pass this object to your dictionary.
This way you could event extend the class to return other stuff, for example all values as a combined string etc... Using public properties you can even set up input validation and what not, but this is probably more than what is needed right now.
I kept the "Class" to the absolute minimum, normally public variables in classes are bad, but since we only use it as custom datatype this does not matter.
Edit: I updatet the class a bit to show some more functionality, but I leave the old one here as an example.
Standard Module "Module1":
Option Explicit
Sub fillDict()
Dim adict As Scripting.Dictionary
Set adict = New Dictionary
Dim info As myRegionData
Dim iter As Long
For iter = 0 To 10
Set info = New myRegionData
info.Region = "someRegion" & iter
info.data = "someData" & iter
info.Time = "someTime" & iter
adict.Add info.Region, info
Next iter
Dim returnInfo As myRegionData
Set returnInfo = adict.Item("someRegion1")
With returnInfo
Debug.Print .Region, .data, .Time 'someRegion1 someData1 someTime1
Debug.Print .fullSentence 'At someTime1 I was in someRegion1 and did someData1
End With
End Sub
Class Module (simple) "myRegionData":
Option Explicit
Public Region As String
Public data As String
Public Time As String
Class Module (extended) "myRegionData":
Option Explicit
Private Type TmyRegionData
'More about this structure:
'https://rubberduckvba.wordpress.com/2018/04/25/private-this-as-tsomething/
Region As String
data As String
Time As String
End Type
Private this As TmyRegionData
Public Property Get Region() As String
Region = this.Region
End Property
Public Property Let Region(value As String)
this.Region = value
End Property
Public Property Get data() As String
data = this.data
End Property
Public Property Let data(value As String)
this.data = value
End Property
Public Property Get Time() As String
Time = this.Time
End Property
Public Property Let Time(value As String)
this.Time = value
End Property
Public Function getFullSentence() As String
getFullSentence = "At " & Time & " I was in " & Region & " and did " & data
End Function
VBA has a dictionary structure. Dictionary is an object, and it can be referenced either with early binding (likeSet countrydict = CreateObject("Scripting.Dictionary")) or with a late binding, referring to Microsoft Scripting Runtime (In VBEditor>Extras>Libraries):
The latter has the advantage, that it is a bit faster and pressing Ctrl+space one would see the Intelli-Sense:
Concerning the question with multiple variables to a dictionary, then an array with those is a possibility:
Sub MyDictionary()
Dim myDict As New Scripting.Dictionary
If Not myDict.Exists("Slim") Then
Debug.Print "Adding Slim"
myDict.Add "Slim", Array("Eminem", "has", "a", "daughter!")
End If
If Not myDict.Exists("Barcelona") Then
Debug.Print "Adding Barcelona"
myDict.Add "Barcelona", Array("I", "have", "been there", 2018)
End If
If Not myDict.Exists("Barcelona") Then
myDict.Add "Barcelona", Array("I", "have", "been there", 2018)
Else
Debug.Print "Barcelona already exists!"
End If
'Keys
Dim key As Variant
For Each key In myDict.Keys
Debug.Print "--------------"
Debug.Print "Key -> "; key
Dim arrItem As Variant
For Each arrItem In myDict(key)
Debug.Print arrItem
Next
Next key
End Sub
This is the result of the code:
Adding Slim
Adding Barcelona
Barcelona already exists!
--------------
Key -> Slim
Eminem
has
a
daughter!
--------------
Key -> Barcelona
I
have
been there
2018
If the value of the dictionary is not an array, e.g. adding somewhere myDict.Add "notArray", 124, an error would pop up once it tries to print the array. This can be avoided with the usage of IsArray built-in function.
I'm using a form to populate a sheet in Excel. In the form, the date-related boxes are broken up by day-month-year combo boxes, which are concatenated to populate a single cell in the sheet. My question is whether there is a way to "unconcatenate" a date in a single cell and populate each individual combo box with the appropriate information (day, month, or year). This is because I want to be able to add new information in one form, then update what already exists in the table in another form.
Code to update form based on given parameter below (focus in Bold):
Private Sub txtstudynm_Change()
Dim StudyName As String
Dim WrdString As String
Dim text_string As String
If Me.txtstudynm.Value = "" Then
MsgBox "Study Name can not be blank", vbCritical
Exit Sub
End If
StudyName = txtstudynm.Value
On Error Resume Next
Me.cmbprojman.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 2, 0)
On Error Resume Next
Me.cmbstudtyp.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 3, 0)
On Error Resume Next
Me.cmbprogtyp.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 4, 0)
On Error Resume Next
Me.cmbfundtyp.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 5, 0)
On Error Resume Next
Me.txtbudget.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 6, 0)
On Error Resume Next
Me.txtencumb.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 7, 0)
On Error Resume Next
Me.cmbpath.Value = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 8, 0)
**On Error Resume Next**
**text_string = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 9, 0).Value**
**WrdString = Split(text_string, "/")(0)**
**Me.tssdcmb1.Value = WrdString**
On Error Resume Next
Me.tssdcmb2.Value = WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 9, 0)
On Error Resume Next
Me.tssdcmb3.Value = WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 9, 0)
End Sub
Instead of all those references to the worksheet, use a Variant array:
Private Sub txtstudynm_Change()
If Me.txtstudynm.Value = "" Then
MsgBox "Study Name can not be blank", vbCritical
Exit Sub
End If
Dim StudyName As String
StudyName = txtstudynm.Value
Dim lkp As Variant
lkp = Sheets("Study Summary").Range("A3:AJ3000").Value2
Dim i As Long
For i = 1 To UBound(lkp, 1)
If lkp(i, 1) = StudyName Then
Me.cmbprojman.Value = lkp(i, 2)
Me.cmbstudtyp.Value = lkp(i, 3)
Me.cmbprogtyp.Value = lkp(i, 4)
Me.cmbfundtyp.Value = lkp(i, 5)
Me.txtbudget.Value = lkp(i, 6)
Me.txtencumb.Value = lkp(i, 7)
Me.cmbpath.Value = lkp(i, 8)
Me.tssdcmb1.Value = Day(lkp(i, 9))
Me.tssdcmb2.Value = Month(lkp(i, 9))
Me.tssdcmb3.Value = Year(lkp(i, 9))
Exit For
End If
Next i
End Sub
Try this (not sure which Combo Box you set to Day/Month/Year)...
Dim StudyDate as Date
If IsDate(text_string) Then
StudyDate = CDate(txt)
Me.tssdcmb1.Value Day(StudyDate)
Me.tssdcmb2.Value Month(StudyDate)
Me.tssdcmb3.Value Year(StudyDate)
End If
Declare text_string as a proper Date value (and this is why you don't encode a variable's data type in its name). Then the VLOOKUP will yield a Date value:
text_string = Application.WorksheetFunction.VLookup(StudyName, Sheets("Study Summary").Range("A3:AJ3000"), 9, 0).Value
If what you have is a proper Date, then you use the VBA.DateTime.Year, VBA.DateTime.Month, and VBA.DateTime.Day functions to get each part:
Me.tssdcmb1.Value = Year(text_string)
Me.MonthBox.Value = Month(text_string)
Me.DayBox.Value = Day(text_string)
Notice how meaningful, pronounceable names make the code easier to read/follow.
If what you have is really a string that looks like a date, then... you better hope that the format is consistent, and then using the VBA.Strings.Split function like you did wasn't a bad idea - just use the 3 indices you got:
Dim dateParts As Variant
dateParts = Split(text_string, "/")
Me.YearBox.Value = dateParts(0)
Me.MonthBox.Value = dateParts(1)
Me.DayBox.Value = dateParts(2)
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.