Boolean value does not properly assign based on conditions in for loop - vba

I am quite new to programming and VBA but I have been using a lot of help questions and answers here on stack overflow to learn! I am attempting to create a loop that will determine if a value from a "task" sheet is missing from a "preview" sheet and if so to add the number and line to the "preview" sheet from the "task" sheet. For an example of the data on each sheet:
Task Sheet
TASK VALUE description
11 task 1
12 task 2
13 task 3
Preview Sheet
PREVIEW VALUE Description
1111 preview 1
2222 preview 2
11 task 1
3333 preview 3
13 task 3
The aim is to compare each number in the preview sheet to each value in the task sheet. If a value in the task sheet is not found in the preview sheet, then it should add that value and the entire line into the preview sheet from the task sheet.
My main approach was to create a for loop that would compare each number on the "preview" sheet to each value on the "task" sheet. If it found that two values matched, it would set the variable newTask = False and then exit the nested for loop to move on to the next comparison. If it found that the value from the data sheet was not present in the main sheet, it would set newTask = True and run through until there was no more values to compare. Then, if newTask = True, it would copy and paste the value and line from the data sheet into the main sheet. This is my attempt at coding this:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
For nT = 1 To iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
If previewWO = taskWO Then
newTask = False
Exit For
ElseIf previewWO <> taskWO Then
newTask = True
End If
Next nT
If newTask = True Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Next n
However, when I run this code, it keeps newTask = True, even if the values do match across the preview and task sheets. Debugging confirmed this and if it comes across similar values, it just continues over it as if they do not equal each other. This causes all values from the task sheet to copy over to the preview sheet, adding many duplicates. I also attempted a Do Until loop but get the same results: Here is my attempt at that:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Do Until taskWO = previewWO Or nT = iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
nT = nT + 1
If nT = iT Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Loop
Next n
I have done a lot of searching but I cannot find any way to get this to stop duplicating values... but I apologize if I missed a thread that has this information or help. I feel like it is something simple but I just cannot figure it out. Could I please get some help on how to correct this? If this is not the correct method, could you also please mention why the loop does not work or what went wrong so I can know in the future? Thank you for any help you provide! If you need some more information, please let me know.

Kyle, without being a complete re-work of your code, here's some code that I think might help you out. If I understand your problem correctly, the main issue you're having is that you're unable to properly determine if a value in one range exists in another. When I have this issue, I usually will use a dictionary object because it's fast, and provides an easy way to check if a value is in a list. To use a dictionary, however, you'll likely have to add a reference to it first. To do this, go to the tools menu and select "References". Scroll down until you've found "Microsoft Scripting Runtime" and select that item. After that, the following code should run just fine.
Sub Testq()
Set Dict = New Dictionary
For Each Cel In Worksheets("preview").Columns(1).SpecialCells(xlCellTypeConstants)
'Add the "Preview Values" of all the cells into your dictionary as Keys.
'Set the value of each key to the "Description" which is in the row next to it.
Dict(Trim(Cel.Value)) = Trim(Cells(Cel.Row(), 2))
Next
'Lets add in the header row of the task worksheet to prevent it from getting coppied over.
Dict(Worksheets("task").Cells(1, 1).Value) = Worksheets("task").Cells(1, 2).Value
'Now loop through all of the values in your "Task" table, checking them against the Dictionary
'to see if there are any new ones.
For Each Cel In Worksheets("Task").Columns(1).SpecialCells(xlCellTypeConstants)
If Not Dict.Exists(Trim(Cel.Value)) Then 'We have a new value.
TaskValue = Trim(Cel.Value)
Description = Trim(Worksheets("Task").Cells(Cel.Row(), 2))
Debug.Print "Yup, I found one that's missing: " & Trim(TaskValue)
'Now add the missing value to the end of your "preview" sheet.
LastRow = Worksheets("preview").Cells(Range("A:A").Rows.Count, "A").End(xlUp).Row
Worksheets("preview").Cells(LastRow + 1, 1) = Trim(TaskValue)
Worksheets("preview").Cells(LastRow + 1, 2) = Trim(Description)
End If
Next
End Sub
Since you mentioned that you're new to VBA, I'll point out that to view the output of the Debug.Print statement, you'll need to display the "Immediate" window. Do this by selecting it from the View menu. As I get a clearer picture of your project, I'll supplement this answer as needed, but for now I hope this will help you solve much of the problem you're having.

Related

how to populate specific cells from a multi column list box - excel vba

I have a list box (lbxStN) with 3 columns (0-fmMultiSelectSingle).
I would like to make a selection (only one) in this list, and have the data from each column of the list shown in a specific cell in a specific sheet (sheet: DeN). First column from the list F19, second column C22 and third column H22. After button (cmdBtnSelect2) is clicked.
Data for the list box is stored in a different sheet in the same workbook.
Private Sub cmdBtnSelect2_Click()
Dim i As Long
Dim myVar4 As String
Dim myVar5 As String
Dim myVar6 As String
For i = 0 To lbxStN.ListCount - 1
If lbxStN.Selected(i) = True Then
lbxStN.List(i, 0).Value = myVar4
lbxStN.List(i, 1).Value = myVar5
lbxStN.List(i, 2).Value = myVar6
End If
Next
ThisWorkbook.Sheets("DeN").Range("F19") = myVar4
ThisWorkbook.Sheets("DeN").Range("C22") = myVar5
ThisWorkbook.Sheets("DeN").Range("H22") = myVar6
End Sub
If I run the code, I get a '424' Object required error.
Which means I'm missing something basic.?
For what you intend to do, there is no need to create new variables. The value from the selected item can be assigned directly to the cells. As follows
Private Sub cmdBtnSelect2_Click()
Dim i as Integer
For i = 0 To lbxStN.ListCount - 1
If lbxStN.Selected(i) Then
ThisWorkbook.Sheets("DeN").Range("F19") = lbxStN.List(i, 0)
ThisWorkbook.Sheets("DeN").Range("C22") = lbxStN.List(i, 1)
ThisWorkbook.Sheets("DeN").Range("H22") = lbxStN.List(i, 2)
Exit For
End If
Next i
End Sub
The previous subroutine iterates for each iteam in the list, and checks if the item is selected. If the item is selected, then it assigns each column to each cell. Exit For will exit the iteration, because there is no need to continue looking for more selected items.
I tested the code with the following subroutine to add items to the list. I think it would be a good idea to compare it with yours in case you have tried to do a complex assignment.
Private Sub CommandButton1_Click()
lbxStN.Clear
lbxStN.AddItem "a"
lbxStN.List(lbxStN.ListCount - 1, 1) = "a2"
lbxStN.List(lbxStN.ListCount - 1, 2) = "a3"
lbxStN.AddItem "b"
lbxStN.List(lbxStN.ListCount - 1, 1) = "b2"
lbxStN.List(lbxStN.ListCount - 1, 2) = "b3"
lbxStN.AddItem "c"
lbxStN.List(lbxStN.ListCount - 1, 1) = "c2"
lbxStN.List(lbxStN.ListCount - 1, 2) = "c3"
End Sub
I usually get better at coding by reading someone elses code and trying to understand it
Cheers!

VBA take screenshot of filtered Excel and send to each row in iteration

I want to run a Macro in Excel to loop through a number of rows, apply a filter to a spreadsheet with the name of the person, take a screenshot, and send an Email to that person with the screenshot.
My current code does not iterate through a range (only 1 record), and does not take a screenshot and insert into email.
Would greatly appreciate assistance with this.
My current code:
Sub SendEmailtoEachResource_Click()
' Macro - Intended functionality
' For each person (resource) apply a filter to the 'Allocation'
' tab, and take a screenshot. Send an email with the screenshot
' to each person.
Dim Resoucename As String
Dim ResouceEmail As String
'Current State: Apply filter, and send 1 email to the below details
ResourceName = Range("D4")
resourceEmail = Range("E4")
'ACTION - Future State:
'For each person in column D
'Send email to email address on same row in Coumn E
' ##Start Loop
'Go to Allocation Tab, Apply Filter of resouce name
Sheets("Allocation").Select
Range("A1:BH28").Select
ActiveSheet.Range("$A$8:$BI$826").AutoFilter Field:=5, Criteria1:= _
ResourceName
ActiveWindow.SmallScroll Down:=-21
ActiveWindow.ScrollRow = 9
Range("A1:BV836").Select
' ACTION: Take Screenshot of filtered results
'setup email
Dim aOutlook As Object
Dim aEmail As Object
Dim outlookResName As String
Dim SendAddress As String
Set aOutlook = CreateObject("Outlook.Application")
Set aEmail = aOutlook.CreateItem(0)
outlookResName = ActiveCell.Value
SendAddress = "me#email.com"
aEmail.To = resourceEmail
aEmail.Subject = "Resource assignment Report for " & ResourceName
aEmail.HTMLBody = "Your report is below {Insert Screenshot}"
'ACTION: Paste screenshot HERE
aEmail.display
' Will change to .send when VBA is working fully. This could send ~100 emails
' ## End LOOP
End Sub
It seems to me that you have two questions rolled up in one here: (1) how to loop through the rows of your spreadsheet and (2) how to take a screenshot and insert it into the e-mail. Maybe you should consider posting two separate questions.
With that in mind, I'll address the loop issue. There are many ways to achieve what you want.
A) You could use the row numbers
For i = 7 To 9
ResourceName = Cells(i, 4)
ResourceEmail = Cells(i, 5)
' The rest of your code here
Next i
B) You could start at the first row and keep moving down until you find an empty cell.
i = 7
Do Until Cells(i, 4) = ""
ResourceName = Cells(i, 4)
ResourceEmail = Cells(i, 5)
' The rest of your code here
i = i + 1
Loop
C) You could give the cells containing the list of resources a name (say, "resources") and loop through its rows.
Set MyList = ActiveWorkbook.Names("resources").RefersToRange
For Each person In MyList.Rows
ResourceName = person.Cells(1, 4)
ResourceEmail = person.Cells(1, 5)
' The rest of your code here
Next person
Why don't you choose one method and then we see where we go from there?

Searching Multi-Column on Excel VBA

I have two problems. First being, in the code below I am trying to search for words in order to find the exact object quicker, but every time, I start the program it starts off blank until I put a character and erase and then everything in the list box appears. I thought taking away Me.allitems.Clearwould fix it, but when I tried to delete the Me.allitems.Clear, it does not affect it.
My second problem is that since I have two columns, I need it to search both columns for the respective word. I tried duplicating the bottom code
If Left(itemsheet.Cells(i, 1).Value, a) = Left(Me.searchbox.Text, a) Then
Me.allitems.AddItem itemsheet.Cells(i, 1).Value
Me.allitems.List(allitems.ListCount - 1, 1) = itemsheet.Cells(i, 2).Value
End If
and adding a 2 to it, but that didn't work.
The whole code:
Private Sub searchbox_Change()
Dim itemsheet As Worksheet
Set itemsheet = Application.ActiveWorkbook.Sheets(6)
Dim i As Long
Me.searchbox.Text = StrConv(Me.searchbox.Text, vbProperCase)
Me.allitems.Clear
For i = 2 To Application.WorksheetFunction.CountA(itemsheet.Range("A:B"))
a = Len(Me.searchbox.Text)
If Left(itemsheet.Cells(i, 1).Value, a) = Left(Me.searchbox.Text, a) Then
Me.allitems.AddItem itemsheet.Cells(i, 1).Value
Me.allitems.List(allitems.ListCount - 1, 1) = itemsheet.Cells(i, 2).Value
End If
Next i
End Sub
Any advice?
EDIT:
I found that the first problem is actually when adding the items into the listbox in this line:
.List(i, 0) = itemnum
.List(i, 1) = Description
i = i + 1
This is the adding the info in the listbox code:
For Each itemname In itemsheet.Range("A2:A3400")
With Me.allitems
.ColumnCount = 2
.ColumnWidths = "60;60"
.AddItem itemname.Value
.List(i, 0) = itemnum
.List(i, 1) = Description
i = i + 1
End With
Next itemname
So specifically the i is the problem, but I need it to add the column
This is how it looks when I first open it up
When I put a character and then erase this appears
This is how it's suppose to look like and I should be able to add it:
For those, who were wondering how to fix this type, it was pretty easy, just add an Or for the columns like this :
If Left(itemsheet.Cells(i, 1).Value, a) = Left(Me.searchbox.Text, a) Or Left(itemsheet.Cells(i, 2).Value, a) = Left(Me.searchbox.Text, a) Then
As for my first problem, still looking for , but for now I have it doing Do Until loop =""
Which is only showing all of the them in one column

Code Not Filling Table Cells Individually, Filling Whole Columns Instead

I have a button that takes the data from a listbox and puts it into specific cells of my table. My problem right now is when inserting the value into the cells it fills the whole column that cell is in instead of the specific cell.
Here is the code for the button:
Private Sub cbSubmit_Click()
Dim i As Long
Dim v As Variant
Dim vTable() As Variant
Set inventoryTable = cSheet.ListObjects("inventory_table")
colItemID = inventoryTable.ListColumns("Item #").Index
colSpecs = inventoryTable.ListColumns("Specs").Index
v = inventoryTable.DataBodyRange.Rows
ReDim vTable(1 To UBound(v, 1), 1 To 4)
For i = 0 To lbItemList.ListCount - 1
vTable(i + 1, 1) = "=DATA!" & lbItemList.List(i, 2)
If specLink = "" Then
Exit For
Else
vTable(i + 1, 4) = lbItemList.List(i, 1)
End If
inventoryTable.DataBodyRange(i + 1, colItemID).Value = vTable(i + 1, 1)
inventoryTable.DataBodyRange(i + 1, colSpecs).Value = vTable(i + 1, 4)
Next
Unload Me
End Sub
This is how it looks after I run the button.
I want it to only fill in the first cell in Item # and then the cell in Specs in that same row. Then go down the rows each cell and fill in the next item. Instead each item gets filled overtop the old items.
If you are targeting individual cells in a structured table (aka ListObject object) then you need to turn of the AutoCorrect.AutoFillFormulasInLists property.
Application.AutoCorrect.AutoFillFormulasInLists = False
This can also be achieved with Alt+F,T,P, Alt+A then go to the AutoFormat As You Type tab and uncheck Fill formulas in tables to create calculated columns.
Optionally turn it back on at the end of your sub procedure if you wish to have this application-wide option available.
Application.AutoCorrect.AutoFillFormulasInLists = True

Populating 2nd+ column of listbox on Excel worksheet

I have an ActiveX listbox on an Excel 2007 worksheet. I want to populate it directly, not by pointing its RowSource property to a range, because there is no range that has the desired values.
The listbox's ColumnCount is set to 2.
I set ColumnWidths to "20;20", and now it returns:
20 pt;20 pt
So as far as I understand, two columns in the listbox should be available for writing, right?
Populating the first column is no problem:
activesheet.lstApplyCurves.List = array("Select All","Deselect All","aaa","bbb","ccc")
(or)
activesheet.lstApplyCurves.additem
activesheet.lstApplyCurves.List(0,0) = "Col1, Row1"
But how do I populate column 2? I get an error 380 ("Could not set the list property. Invalid property value.") on this:
activesheet.lstApplyCurves.List(0,1) = "Col2, Row1"
FWIW I've also tried this, but get the same error:
activesheet.lstApplyCurves.List(1,1) = "Col2, Row2"
So...how do I set values in the 2nd column?
UPDATE:
In addition to the answer below, FWIW I also found it's possible to assign a mulit-dimensional array to the List property, which is faster:
Dim ArrayToListbox() As Variant
ReDim ArrayToListbox(0 To 4, 0 To 2)
ArrayToListbox(0, 0) = "Select All"
ArrayToListbox(1, 0) = "Deselect All"
ArrayToListbox(2, 0) = "Row1-Col1"
ArrayToListbox(2, 1) = "Row1-Col2"
ArrayToListbox(2, 2) = "Row1-Col3"
ArrayToListbox(3, 0) = "Row2-Col1"
ArrayToListbox(3, 1) = "Row2-Col2"
ArrayToListbox(3, 2) = "Row2-Col3"
ArrayToListbox(4, 0) = "Row3-Col1"
ArrayToListbox(4, 1) = "Row3-Col2"
ArrayToListbox(4, 2) = "Row3-Col3" '"(" & Join(Array("a", "b", "c"), "|") & ")"
ActiveSheet.lstApplyCurves.Clear
ActiveSheet.lstApplyCurves.ColumnCount = 3
ActiveSheet.lstApplyCurves.List = ArrayToListbox
This works for me. If the below doesn't work on your system then delete the listbox and re-create it and then try this code again.
Private Sub CommandButton1_Click()
With ListBox1
.Clear
.ColumnCount = 2
For i = 1 To 2
.AddItem
.List(i - 1, 0) = "Col1, Row" & i
.List(i - 1, 1) = "Col2, Row" & i
Next i
End With
End Sub