Creating checkboxes dynamically from array VBA - arrays

I have a userform as following:
When I pick the database I want and click select, it gets me to the other page:
All those checkboxes are created depending on the output of an sql query.
In this example, the SQL query is:
ID NAME
8 AI_EQ
14 BB_EQ
19 DM_EQ
22 GD_EQ
and so on...
I am adding the checkboxes in the select button as following (from a record set rst):
'creating checkboxes
If Not rst.EOF And Not rst.BOF Then
rst.MoveFirst
i = 0
Do
With MultiPage1.Pages(1).Controls.Add("Forms.Checkbox.1", "Checkbox" & i)
.Top = yPos
.Left = 7
.Caption = rst![name]
.Width = 450
.Height = 24
.WordWrap = True
.Value = False
yPos = yPos + 17
i = i + 1
rst.MoveNext
End With
Loop Until rst.EOF
End If
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
Then, I would want the Finish button considering whatever checkboxes the user selects. For example, if he selects the first two, I would want to have "8,14" ID, so that I can add them into a new SQL query.
I guess I have to create an array to achieve this instead of just having rst![name]. But my attempts of creating it have failed, and even if I had the array correctly, I wouldn't know how to adapt it to have it return the ID when selecting the NAME.
This is how I am trying to create the array (but it is not returning anything)
Dim MyArray() As Variant
For i = 0 To rst.RecordCount
MyArray = rst.GetRows(i)
rst.MoveNext
Next
Any hints/help would be appreciated.

Since the checkboxes are created dynamically, do this.
Loop through all controls and check if they are checkbox or not
Check if they are checked
Check if they have something in .Tag and store it in a variable.
Code
Private Sub CommandButton1_Click()
Dim cCont As Control
Dim cbString As String
For Each cCont In Me.Controls
If TypeName(cCont) = "CheckBox" Then
If cCont.Value = True Then
If cCont.Tag <> "" Then
If cbString = "" Then
cbString = cCont.Tag
Else
cbString = cbString & "," & cCont.Tag
End If
End If
End If
End If
Next cCont
Debug.Print cbString
End Sub
But for this to work, when you are creating the checkbox, set the .Tag property to the ID value.
With MultiPage1.Pages(1).Controls.Add("Forms.Checkbox.1", "Checkbox" & i)
'
'~~> Rest of the code
'
.Tag = rst![ID]
'
'~~> Rest of the code
'
End With

As mentioned Siddharth Rout:
'creating checkboxes
If Not rst.EOF And Not rst.BOF Then
rst.MoveFirst
i = 0
Do
With MultiPage1.Pages(1).Controls.Add("Forms.Checkbox.1", "Checkbox" & i)
.Top = yPos
.Left = 7
.Caption = rst![name]
.Width = 450
.Height = 24
.WordWrap = True
.Value = False
yPos = yPos + 17
.Tag = rst![ID]
i = i + 1
rst.MoveNext
End With
Loop Until rst.EOF
End If
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing

Related

Transpose Filtered Column As String to Cell

I have a table which looks like this:
I wrote code which gives output like this:
The goal is a results table which does the following:
Count number of times "old" status appears
Count numer of times "new" status appears
Get all the (unique) old groups in one cell
Get all the (unique) new groups in one cell
The following code worked on one computer but not on another (both Windows, 64bit):
Sub TableSummary()
Dim sht As Worksheet
Dim i As Integer
Dim tbl As ListObject
Dim new_tbl As ListObject, old_tbl As ListObject
Dim new_array As Variant, old_array As Variant
'2. Disable Screen Updating - stop screen flickering and Disable Events to avoid inturupted dialogs / popups
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Application.DisplayAlerts = False
On Error Resume Next
Application.DisplayAlerts = True
'4. Add a new summary table to summary worksheet
With ActiveWorkbook
sht.ListObjects.Add(xlSrcRange, sht.UsedRange, , xlYes).Name = "Summary"
sht.ListObjects("Summary").TableStyle = "TableStyleMedium5"
End With
i = 1
For Each sht In ActiveWorkbook.Worksheets
If sht.Name = "Summary" Then
'Define Column Headers of Summary
sht.Cells(1, 4).Resize(1, 4).Value = Array("Nbr of old", "Nbr of new", "Groups old", "Groups new")
i = i + 1
For Each tbl In sht.ListObjects
' Blue table
If tbl.TableStyle = "TableStyleMedium2" Then
sht.Range("D" & i).Value = WorksheetFunction.CountIf(tbl.Range, "old")
sht.Range("E" & i).Value = WorksheetFunction.CountIf(tbl.Range, "new")
Set new_tbl = sht.ListObjects("Summary")
Set new_tbl = sht.ListObjects("Summary").Range().AutoFilter(Field:=2, Criteria1:="old")
new_array = Application.Transpose(WorksheetFunction.Unique(sht.ListObjects("Summary").ListColumns("Group").DataBodyRange.SpecialCells(xlCellTypeVisible))) 'This doesn't work on my other machine
sht.Range("F" & i).Value = Join(new_array, ", ") 'works!
'Debug.Print Join(new_array, ", ")
sht.ListObjects("Summary").AutoFilter.ShowAllData
Set new_tbl = sht.ListObjects("Summary")
Set new_tbl = sht.ListObjects("Summary").Range().AutoFilter(Field:=2, Criteria1:="new")
new_array = Application.Transpose(WorksheetFunction.Unique(sht.ListObjects("Summary").ListColumns("Group").DataBodyRange.SpecialCells(xlCellTypeVisible))) 'This doesn't work on my other machine
sht.Range("G" & i).Value = Join(new_array, ", ") 'works!
Debug.Print Join(new_array, ", ")
sht.ListObjects("Summary").AutoFilter.ShowAllData
End If
Next
End If
Next
End Sub
Application.Transpose does not work on my second machine.
Here's a different approach using a function to create the list of unique values:
Sub TableSummary()
Const NEW_OLD_COL As Long = 2
Const GROUP_COL As String = "Group"
Const VAL_OLD As String = "old"
Const VAL_NEW As String = "new"
Dim sht As Worksheet, DstSht As Worksheet
Dim i As Integer
Dim tbl As ListObject
Dim new_tbl As ListObject, old_tbl As ListObject
Dim new_array As Variant, old_array As Variant
Set sht = ActiveSheet 'or whatever...
Set DstSht = sht
i = 2
For Each tbl In sht.ListObjects
' Blue table
If tbl.TableStyle = "TableStyleMedium2" Then
With tbl.ListColumns(NEW_OLD_COL)
DstSht.Range("G" & i).Value = WorksheetFunction.CountIf(.DataBodyRange, VAL_OLD)
DstSht.Range("H" & i).Value = WorksheetFunction.CountIf(.DataBodyRange, VAL_NEW)
End With
tbl.Range.AutoFilter Field:=NEW_OLD_COL, Criteria1:="new"
DstSht.Range("I" & i).Value = VisibleUniques(tbl, GROUP_COL)
tbl.Range.AutoFilter
tbl.Range.AutoFilter Field:=NEW_OLD_COL, Criteria1:="old"
DstSht.Range("J" & i).Value = VisibleUniques(tbl, GROUP_COL)
tbl.Range.AutoFilter
i = i + 1
End If
Next
End Sub
'Return a comma-separated list of all unique values in visible cells in
' column `ColName` of listobject `tbl`
Function VisibleUniques(tbl As ListObject, ColName As String) As String
Dim rngVis As Range, dict As Object, c As Range
On Error Resume Next 'ignore error if no visible cells
Set rngVis = tbl.ListColumns(ColName).DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0 'stop ignoring errors
If rngVis Is Nothing Then Exit Function
Set dict = CreateObject("scripting.dictionary")
For Each c In rngVis.Cells
dict(CStr(c.Value)) = True
Next c
VisibleUniques = Join(dict.keys, ", ")
End Function

VBA Create array with userform items

I have a a list of checkbox in userform. I would like to create a code that is more efficient that the following one :
If checkbox1.value = true
Then Range("A1").value = 100
End if
If checkbox2.value = true
Then Range("A2").value = 200
End if
If checkbox3.value = true
Then Range("A3").value = 300
End if
The problem is that I have 40 checkbox and I would like to create something that says: in my list of checkbox, if it is true then add in my range A1. I'm not sure how to proceed but I tried this:
Dim Element as variant
For each element in MyList
If element.value = true Then
For i = 1 to NumberOfTrueElement
Range("A" & i + 1).value = Mylist(i)
Next i
End if
Next
Mylist is the frame of all my checkboxes.
Please help me if you have a hint.
It's not really clear where the array part comes into thing but if we assume a couple of things,
You have an array named Mylist with 40 items.
The checkboxes are named consistently e.g. checkbox1, checkbox2 etc
then you could use something like this.
For I = 1 To 40
If Me.Controls("checkbox" & I).Value
Range("A" & I).Value = Mylist(I)
End If
Next I
Please, try this way:
Dim i As Long, k As Long
For k = 0 To Me.Controls.count - 1
If TypeName(Me.Controls(k)) = "CheckBox" Then
i = i + 1
If Me.Controls("checkbox" & i).value Then
Range("A" & i).value = i * 100
End If
End If
Next k
If you need a faster way, please test the next code:
Dim i As Long, k As Long, arr
ReDim arr(Me.Controls.count)
For k = 0 To Me.Controls.count - 1
If TypeName(Me.Controls(k)) = "CheckBox" Then
i = i + 1
If Me.Controls("checkbox" & i).value Then
arr(i - 1) = i * 100
Else
arr(i - 1) = "False"
End If
End If
Next k
ReDim Preserve arr(i - 1)
Range("A1").Resize(UBound(arr) + 1, 1) = Application.Transpose(arr)
It loads the necessary values in an array, using "False" when the check box is not checked and drops the array values at once at the end.
Of course, the check boxes name should be consistently built. Starting from "checkbox1" to "checkboxn". If the name consistence exists, the code works for as many check boxes exist on the form...
Write Value If Checkbox Checked
A kind of an answer would be in the CommandButton_Click procedure.
This example is easily setup by anyone: open a new workbook, add a user form and add a command button on it. Double-click the command button and copy the following code to the just-opened window (user form code). Run the first procedure. Tick a few checkboxes and press the command button. See the copied values in the first column of the worksheet.
The Code
Option Explicit
Sub doShow()
UserForm1.Show
End Sub
Private Sub CommandButton1_Click()
Dim arr() As Variant: ReDim arr(1 To Me.Controls.Count)
Dim chb As MSForms.Control
Dim n As Long
For Each chb In Me.Controls
If TypeName(chb) = "CheckBox" Then
n = n + 1
If chb.Value = True Then
arr(n) = n * 100
End If
End If
Next chb
ReDim Preserve arr(1 To n)
With ThisWorkbook.Worksheets("Sheet1")
.Range("A1").Resize(n).Value = Application.Transpose(arr)
End With
End Sub
Private Sub UserForm_Initialize()
Const CheckBoxesCount As Long = 10
Dim n As Long
For n = 1 To CheckBoxesCount
With Me.Controls.Add("Forms.CheckBox.1", "CheckBox_" & n, True)
.Caption = "CheckBox" & n
.Left = 5
.Top = 5 + ((n - 1) * 20)
End With
Next n
End Sub

Compare two sheets and highlight differences on each sheet - is looping the only way?

I am seeking advice in relation to improving performance for a large data set (roughly 175k lines on each sheet and 39 columns A:AM (comparing sheet1 vs sheet2). These sheets are exported from access and my VBA is written in Access. I have coding that employs a "for" loop that checks cell by cell and highlights if there is a mismatch in each relative cell.
My question - will using an array or dictionary function speed up the process? If yes, can you provide bread crumbs to shed some light on how to execute? This code currently takes approximately 3 hours to complete. Roughly 2 minutes for the export from Access to Excel and the rest of the time represents looping and highlighting.
As a note - I have written code for conditional formatting and that works incredibly fast. The main problem is that I am unable to copy/paste the sheets with highlighted cells into new sheets while leaving the conditions behind. I would be interested to hear if any have found a way to maneuver that mine field.
Code below:
DoCmd.SetWarnings False
Dim xlapp As Excel.Application
Dim xlbook As Excel.Workbook
Dim xlSheet, xlSheetPre, xlSheetPost As Excel.Worksheet
Dim SQL As String
Dim rs1 As DAO.Recordset
Dim iSheet As Long, iRow As Long, iCol As Long, cols As Long
Dim MaxLastRow As Long, MaxLastCol As Long
Dim LastRow1 As Range, LastRow2 As Range
Dim LastCol1 As Range, LastCol2 As Range
Dim i As Integer
SQL = "SELECT * From Pre"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set xlapp = Excel.Application
xlapp.Visible = True
Set xlbook = xlapp.Workbooks.Add
i = 1
Do
Set xlSheet = Sheets.Add(after:=Sheets(Sheets.Count))
i = i + 1
Loop Until i = 2 ' the number 2 represents how many sheets you want to add to the
workbook
Set xlSheet = xlbook.Worksheets(1) ' Finds worksheet (1) and begins loading data from SQL
table above
With xlSheet
.Name = "Pre" ' Name the worksheet
.Range("a1:am1").Font.Bold = True 'Converts headers in row 1 to
bold font
.Range("A2").CopyFromRecordset rs1 'Copies all data from selected
table (SQL)into your worksheet
.Range("a1").AutoFilter ' Adds filter to your columns
.Cells.Columns.AutoFit ' Adjust worksheet column width to autofit
your data
.Range("a1:am1").Interior.ColorIndex = 37 ' Changes color of cell
' This loop reads all headers in your access table and places
them on worksheet
For cols = 0 To rs1.Fields.Count - 1
.Cells(1, cols + 1).Value = rs1.Fields(cols).Name
Next
End With
SQL = "SELECT * From Post"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set xlSheet = xlbook.Worksheets(2)
With xlSheet
.Name = "Post" ' Name the worksheet
.Range("a1:am1").Font.Bold = True 'Converts headers in row 1 to
bold font
.Range("A2").CopyFromRecordset rs1 'Copies all data from selected
table (SQL)into your worksheet
.Range("a1").AutoFilter ' Adds filter to your columns
.Cells.Columns.AutoFit ' Adjust worksheet column width to autofit
your data
.Range("a1:am1").Interior.ColorIndex = 37 ' Changes color of cell
' This loop reads all headers in your access table and places
them on worksheet
' This loop reads all headers in your access table and places them on worksheet
For cols = 0 To rs1.Fields.Count - 1
.Cells(1, cols + 1).Value = rs1.Fields(cols).Name
Next
End With
Set xlSheetPre = xlbook.Worksheets(1)
Set xlSheetPost = xlbook.Worksheets(2)
Set LastRow1 = xlSheetPre.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
Set LastRow2 = xlSheetPost.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
If Not LastRow1 Is Nothing Then
If Not LastRow2 Is Nothing Then
If LastRow1.Row > LastRow2.Row Then
MaxLastRow = LastRow1.Row
Else
MaxLastRow = LastRow2.Row
End If
Else
MaxLastRow = LastRow1.Row
End If
Else
MaxLastRow = LastRow2.Row
End If
Set LastCol1 = xlSheetPre.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
Set LastCol2 = xlSheetPost.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious)
If Not LastCol1 Is Nothing Then
If Not LastCol2 Is Nothing Then
If LastCol1.Column > LastCol2.Column Then
MaxLastCol = LastCol1.Column
Else
MaxLastCol = LastCol2.Column
End If
Else
MaxLastCol = LastCol1.Column
End If
Else
MaxLastCol = LastCol2.Column
End If
For iRow = 2 To MaxLastRow 'starting loop on row 2
For iCol = 4 To MaxLastCol 'starting loop on column 4
If xlSheetPre.Cells(iRow, iCol).Value <> xlSheetPost.Cells(iRow, iCol).Value Then
xlSheetPre.Cells(iRow, iCol).Interior.ColorIndex = 4
xlSheetPost.Cells(iRow, iCol).Interior.ColorIndex = 4
End If
Next iCol
Next iRow
SubExit:
On Error Resume Next
rs1.Close
Set rs1 = Nothing
DoCmd.SetWarnings True
Exit Sub
Try and reduce the number of records you have to compare by only extracting those with differences. There are several ways you could do that in SQL but as a proof of concept this compares each column in turn creating a temporary table of keys which is used to filter the records extracted.
Option Compare Database
Option Explicit
Sub DumpToExcel()
Dim n As Integer, SQL As String, fname
' field names
fname = Array("", "F1", "F2", "F3", "F4", "F5", _
"F6", "F7", "F8", "F9", "F10")
' identify diff records
Debug.Print UBound(fname)
DoCmd.SetWarnings False
For n = 1 To UBound(fname)
If n = 1 Then ' create table
SQL = " SELECT post.ID, """ & n & """ AS Col INTO tmp"
Else
SQL = " INSERT INTO tmp" & _
" SELECT post.ID, """ & n & """ AS Col"
End If
SQL = SQL & _
" FROM Post LEFT JOIN pre ON Post.id = pre.id" & _
" WHERE NZ([pre].[" & fname(n) & "],"")<>NZ([post].[" & fname(n) & "],"");"
DoCmd.RunSQL SQL
Next
DoCmd.SetWarnings True
' extract data
Dim rs1 As DAO.Recordset
SQL = " SELECT * FROM pre" & _
" WHERE (((pre.[ID]) In " & _
" (SELECT DISTINCT(ID) FROM tmp )));"
Set rs1 = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
' create excel
Dim xlapp As Excel.Application, xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
Set xlapp = Excel.Application
xlapp.Visible = True
Set xlBook = xlapp.Workbooks.Add
'add sheets as required
Do While xlBook.Sheets.Count < 2
xlBook.Sheets.Add
Loop
' copy recordset to sheet
xlBook.Sheets(1).Range("A2").CopyFromRecordset rs1
MsgBox "Done"
End Sub
"My question - will using an array or dictionary function speed up the process?"
Speaking from experience, the answer is: No, it will not. The reason is that you will have to read the cells in the worksheet to populate an array or a dictionary in the first place, so... Looping is it, really, and you need to organize the data (usually by proper sorting of the lists, tables, ranges, whatever) to minimize searching for the matching records (rows) to make your loops run faster.
If you are in Access then you can do that directly with the recordsets, providing your company's network security does not interfere with movement withing recordset objects (mine does interfere, and very severely at that--Tanium is a real menace!)
Here's an array-based comparison.
Compiled but not tested:
Sub Tester()
Dim xlapp As Excel.Application
Dim xlbook As Excel.Workbook
Dim xlSheet, wsPre As Excel.Worksheet, wsPost As Excel.Worksheet
Dim rowsPost As Long, rowsPre As Long, rowsMax As Long
Dim colsPre As Long, colsPost As Long, colsMax As Long, flag As Boolean
Dim r As Long, c As Long, rngPre As Range, rngPost As Range, arrPre, arrPost
DoCmd.SetWarnings False
Set xlapp = New Excel.Application 'forgot "New" here?
xlapp.Visible = True
Set xlbook = xlapp.Workbooks.Add()
Do While xlbook.Worksheets.Count < 2 'how many sheets you need in the Workbook
xlbook.Sheets.Add
Loop
Set wsPre = xlbook.Worksheets(1)
Set wsPost = xlbook.Worksheets(2)
PutInWorksheet "SELECT * From Pre", wsPre, "Pre"
PutInWorksheet "SELECT * From Post", wsPost, "Post"
Set rngPre = wsPre.Range("A1").CurrentRegion 'data ranges
Set rngPost = wsPost.Range("A1").CurrentRegion
arrPre = rngPre.Value 'read data to arrays
arrPost = rngPost.Value
rowsPre = UBound(arrPre, 1) 'compare array bounds...
rowsPost = UBound(arrPost, 1)
rowsMax = xlapp.Max(rowsPre, rowsPost)
colsPre = UBound(arrPre, 2)
colsPost = UBound(arrPost, 2)
colsMax = xlapp.Max(colsPre, colsPost)
For r = 2 To rowsMax
flag = (r > rowsPre) Or (r > rowsPost) 'flag whole row if have run out of data in one set...
If flag Then
FlagRanges rngPre.Cells(r, 1).Resize(1, colsMax), _
rngPost.Cells(r, 1).Resize(1, colsMax)
Else
'have two rows to compare
For c = 1 To colsMax
flag = (c > colsPre) Or (c > colsPost) 'run out of cols in one dataset?
If Not flag Then
flag = arrPre(r, c) <> arrPost(r, c) 'compare data
End If
If flag Then
'no data to compare, or data does not match
FlagRanges rngPre.Cells(r, c), rngPost.Cells(r, c)
End If
Next c
End If
Next r
End Sub
Sub FlagRanges(rng1 As Excel.Range, rng2 As Excel.Range)
Const CLR_INDX = 4
rng1.Interior.ColorIndex = CLR_INDX
rng2.Interior.ColorIndex = CLR_INDX
End Sub
'run a query and put the results on a worksheet starting at A1
Sub PutInWorksheet(SQL As String, ws As Excel.Worksheet, _
Optional newName As String = "")
Dim f, c As Excel.Range, rs As dao.Recordset
If Len(newName) > 0 Then ws.Name = newName
Set rs = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set c = ws.Range("A1")
For Each f In rs.Fields
c.Value = f.Name
c.Font.Bold = True
Next f
ws.Range("A2").CopyFromRecordset rs
rs.Close
End Sub

vba for each element loop error occurs at second loop

I'm new to VBA and I'm trying to scrape data from a website. I've used nested loop. When the innermost loop finishes for the first time, the next loop starts for marakez.
Actual problem is that when 'for each in schl2' loop repeats for second time, IE crashes and loop is unable to proceed. I have mentioned in code.
Here is my code
Sub ResultDownloader()
' here I define elemnts for the loop
Dim sht As Worksheet
Set sht = ThisWorkbook.Sheets("LocData")
Dim LastRow As Long
Dim i As Long
Dim imagePath As Object
LastRow = sht.Cells(sht.Rows.Count, "D").End(xlUp).Row
startrec = sht.Cells(sht.Rows.Count, "E").End(xlUp).Row
startrec = startrec + 1
Dim IE As Object
Dim Doc As HTMLDocument
' Set IE = CreateObject("InternetExplorer.Application")
Set IE = CreateObject("InternetExplorer.Application")
' here I define Object to sendkeys
Dim SHELL_OBJECT
SHELL_OBJECT = "WScript.Shell"
Set objShell = CreateObject(SHELL_OBJECT)
Record2Strt = (sht.Cells(sht.Rows.Count, "E").End(xlUp).Row) + 1
IE.Visible = True
IE.Navigate "some_url"
Do While IE.Busy
Application.Wait DateAdd("s", 1, Now)
Loop
Dim HTMLdoc As HTMLDocument
Dim selectElement, selectElement2, selectElement3 As HTMLSelectElement
Dim evtChange As Object
Set Doc = IE.Document
Dim dist1, tehsl1, mrkz1, schl1 As Object
Dim dist2, tehsl2, mrkz2, schl2 As Variant
Dim distlen, thsllen, mrkzlen, schllen As Byte
Dim dst, tsl, mrkz, schl As Byte
Dim elt3, elt4, elt5, elt6 As Variant
Set evtChange = Doc.createEvent("HTMLEvents")
evtChange.initEvent "change", True, False
Set dist1 = Doc.querySelector("Select[name=districts]")
Set dist2 = dist1.querySelectorAll("option")
distlen = dist1.querySelectorAll("option").Length
dst = 0
For Each elt3 In dist2
distnme = elt3.innerText
If distnme <> "All Districts" Then
dist1.getElementsByTagName("option")(dst).Selected = True
Set selectElement2 = Doc.getElementsByTagName("option")(dst)
selectElement2.dispatchEvent evtChange
Application.Wait DateAdd("s", 0.5, Now)
Set tehsl1 = Doc.querySelector("Select[name=tehsil]")
Set tehsl2 = tehsl1.querySelectorAll("option")
thsllen = tehsl1.querySelectorAll("option").Length
tsl = 0
For Each elt4 In tehsl2
thslnme = elt4.innerText
If thslnme <> "All Tehsils" Then
Set tehsl1 = Doc.querySelector("Select[name=tehsil]")
tehsl1.getElementsByTagName("option")(tsl).Selected = True
Set selectElement3 = tehsl1.getElementsByTagName("option")(tsl)
selectElement3.dispatchEvent evtChange
Application.Wait DateAdd("s", 0.5, Now)
Set mrkz1 = Doc.querySelector("Select[name=markaz]")
Set mrkz2 = mrkz1.querySelectorAll("option")
mrkzlen = mrkz1.querySelectorAll("option").Length
mrkz = 0
For Each elt5 In mrkz2
mrkznm = elt5.innerText
If mrkznm <> "All Marakez" Then
Set mrkz1 = Doc.querySelector("Select[name=markaz]")
mrkz1.getElementsByTagName("option")(mrkz).Selected = True
Set selectElement4 = mrkz1.getElementsByTagName("option")(mrkz)
selectElement4.dispatchEvent evtChange
Application.Wait DateAdd("s", 0.5, Now)
Set schl1 = Doc.querySelector("Select[name=school]")
Set schl2 = schl1.querySelectorAll("option")
schllen = schl1.querySelectorAll("option").Length
schl = 0
' second loop problem
' when for each elt6 in schl2 starts IE crashes
On Error Resume Next
For Each elt6 In schl2
Application.Wait DateAdd("s", 0.5, Now)
schlnm = elt6.innerText
If schlnm <> "All Schools" Then
Set schl1 = Doc.querySelector("Select[name=school]")
schl1.getElementsByTagName("option")(schl).Selected = True
Set selectElement5 = schl1.getElementsByTagName("option")(schl)
selectElement5.dispatchEvent evtChange
sht.Range("A" & LastRow + 1).Value = LastRow
sht.Range("B" & LastRow + 1).Value = distnme
sht.Range("C" & LastRow + 1).Value = thslnme
sht.Range("D" & LastRow + 1).Value = mrkznm
sht.Range("E" & LastRow + 1).Value = schlnm
LastRow = LastRow + 1
End If 'for school
schl = schl + 1
If schllen = schl Then
GoTo new_marakez
On Error Resume Next
End If
Next 'ele6
End If 'for marakez
new_marakez:
mrkz = mrkz + 1
If mrkzlen = mrkz Then
Exit For
GoTo new_tehsil
End If
Next 'ele5
On Error Resume Next
End If 'for tehsils
new_tehsil:
tsl = tsl + 1
If thsllen = tsl Then
GoTo new_dist
End If
Next 'ele4
On Error Resume Next
End If 'for districts
new_dist:
dst = dst + 1
If distlen = dst Then
GoTo stopp
End If
Next 'ele 3
On Error Resume Next
stopp:
End Sub
There is apparently a bug when using querySelectorAll with a generic object for your elements, in your case here 'schl2.', and using a for each...next loop. I solved this by using a standard for...next loop basically limiting the for loop, in your case, schl2.Length - 1. However, this will not work unless you define schl2 as MSHTML.IHTMLDOMChildrenCollection. If you leave this as generic, the schl2.Length will be NULL. The code below shows how I got around the problem.
`'Create html object to hold IE Document
Set html = IE.Document
Debug.Print "********* GET FIELDS ******" & vbCrLf
Dim res1 As MSHTML.IHTMLDOMChildrenCollection
Set res1 = html.querySelectorAll("#HtmlOutputReportResults2_Explorer_Filters_Column option:checked")
For r = 0 To res1.Length - 1
If res1(r).innerText <> "..." Then
Debug.Print "res1.Text: " & res1(r).innerText
End If
Next
Debug.Print vbCrLf & "********* GET OPERATORS ******" & vbCrLf
Dim res2 As MSHTML.IHTMLDOMChildrenCollection
Set res2 = html.querySelectorAll("#HtmlOutputReportResults2_Explorer_Filters_Operator option:checked")
For r = 0 To res2.Length - 1
If res2(r).innerText <> "..." Then
Debug.Print "res2.Text: " & res2(r).innerText
End If
Next`

VBA Excel Counting Specific Values

I'm trying to write a program that will loop through cells of a specific column (assigned by the user), find new values in those cells and count how many times a specific value is found. The main problem I'm having right now is that this is hard-coded like below:
Function findValues() As Long
For iRow = 2 To g_totalRow
If (ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text = "") Then
nullInt = nullInt + 1
ElseIf (someValue1 = "" Or someValue1 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue1 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt1 = someInt1 + 1
ElseIf (someValue2 = "" Or someValue2 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue2 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt2 = someInt2 + 1
ElseIf (someValue3 = "" Or someValue3 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue3 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt3 = someInt3 + 1
ElseIf (someValue4 = "" Or someValue4 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue4 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt4 = someInt4 + 1
ElseIf (someValue5 = "" Or someValue5 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue5 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt5 = someInt5 + 1
ElseIf (someValue6 = "" Or someValue6 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue6 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt6 = someInt6 + 1
ElseIf (someValue7 = "" Or someValue7 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue7 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt7 = someInt7 + 1
ElseIf (someValue8 = "" Or someValue8 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue8 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt8 = someInt8 + 1
ElseIf (someValue9 = "" Or someValue9 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue9 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt9 = someInt9 + 1
ElseIf (someValue10 = "" Or someValue10 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text) Then
someValue10 = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
someInt10 = someInt10 + 1
End If
Next iRow
End Function
Here, if the ActiveCell is blank then the nullInt will get incremented, if the ActiveCell has some value then it'll find which of the variables has that same value or the ActiveCell value will be assigned to one of the variables. I created ten variables strictly for testing purposes but I need to make up to one hundred. I was wondering if there was a way to complete this quickly. The only way I could think of was to create a String array and an Int array and store the values that way. However I'm not sure if this is the best way to get this done.
Edit
This portion is directed specifically to dictionaries. Say there is a specific column titled "State". This contains the 50 North American states. Some of these states are repeated and there is a total of 800 values in this column. How do I keep track of how many times (for example) Texas gets hit?
Thank you,
Jesse Smothermon
You should be able to do this with a Dictionary (see Does VBA have Dictionary Structure?)
This code hasn't been tested but should give you a start.
Function findValues() As Scripting.Dictionary
Dim cellValue
Dim dict As New Scripting.Dictionary
For iRow = 2 To g_totalRow
cellValue = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
If dict.Exists(cellValue) Then
dict.Item(cellValue) = dict.Item(cellValue) + 1
Else
dict.Item(cellValue) = 1
End If
Next iRow
Set findValues = dict
End Function
Sub displayValues(dict As Scripting.Dictionary)
Dim i
Dim value
Dim valueCount
For i = 1 To dict.count
valueCount = dict.Items(i)
value = dict.Keys(i)
ActiveWorkbook.Sheets(sheetName).Cells(i, 3).Text = value
ActiveWorkbook.Sheets(sheetName).Cells(i, 4).Text = valueCount
Next i
End Sub
Sub RunAndDisplay()
Dim dict
Set dict = findValues
displayValues dict
End Sub
I've drafted a code for you, hope it helps. I added comments to make each step clearer for you. I believe that simply setting the proper values in the 1st step might make it work for you.
Still, would worth to understand what the code does to help you in the future.
Hope it fits your needs!
Option Explicit
Sub compareValues()
Dim oSource As Excel.Range
Dim oColumn As Excel.Range
Dim oCell As Excel.Range
Dim sBookName As String
Dim sSheetCompare As String
Dim sSheetSource As String
Dim sUserCol As String
Dim sOutputCol As String
Dim sFirstCell As String
Dim vDicItem As Variant
Dim sKey As String
Dim iCount As Integer
Dim sOutput As String
Dim oDic As Scripting.Dictionary
'1st - Define your source for somevalues and for the data to be compared
sBookName = "Book1"
sSheetCompare = "Sheet1"
sSheetSource = "Sheet2"
sFirstCell = "A1"
sOutputCol = "C"
'2nd - Define the 'somevalues' origin value; other values will be taken
' from the rows below the original value (i.e., we'll take our
' somevalues starting from sSheetSource.sFirstCell and moving to the
' next row until the next row is empty
Set oSource = Workbooks(sBookName).Sheets(sSheetSource).Range(sFirstCell)
'3rd - Populate our dictionary with the values beggining in the sFirstCell
populateDic oSource, oDic
'At this stage, we have all somevalues in our dictionary; to check if the
' valuesare as expected, uncomment the code below, that will print into
' immediate window (ctrl+G) the values in the dictionary
For Each vDicItem In oDic
Debug.Print vDicItem
Next vDicItem
'4th - ask the user for the column he wants to use; Use single letters.
' E.g.: A
sUserCol = InputBox("Enter the column the data will be compared")
'5th - scan the column given by the user for the values in the dictionary
Set oColumn = Workbooks(sBookName).Sheets(sSheetCompare).Columns(sUserCol)
'6th - Now, we scan every cell in the column
For Each oCell In oColumn.Cells
sKey = oCell.Value
'7th - Test the special case when the cell is empty
If sKey = "" Then oDic("Empty") = oDic("Empty") + 1
'8th - Test if the key value exists in the dictionary; if so, add it
If oDic.Exists(sKey) Then oDic(sKey) = oDic(sKey) + 1
'9th - Added to exit the for when row reaches 1000.
If oCell.Row = 1000 Then Exit For
Next oCell
'10th - Now, we print back the counters we found, only for sample purposes
' From now on, is up to you how to use the dictionary :)
iCount = 1
Set oColumn = Workbooks(sBookName).Sheets(sSheetCompare).Columns(sOutputCol)
Set oCell = oColumn.Cells(1, 1)
For Each vDicItem In oDic
If oDic(vDicItem) > 0 Then
oCell.Value = vDicItem
oCell.Offset(0, 1).Value = oDic(vDicItem)
Set oCell = oCell.Offset(1, 0)
End If
Next vDicItem
End Sub
Sub populateDic(ByRef oSource As Excel.Range, _
ByRef oDic As Scripting.Dictionary)
'Ideally we'd test if it's created. Let's just set it for code simplicity
Set oDic = New Scripting.Dictionary
'Let's add an 'empty' counter for the empty cells
oDic.Add "Empty", 0
While Len(oSource.Value) > 0
'If the data is not added into somevalues dictionary of values, we add
If Not oDic.Exists(oSource.Value) Then oDic.Add CStr(oSource.Value), 0
'Move our cell to the next row
Set oSource = oSource.Offset(1, 0)
Wend
End Sub

Resources