Excel VBA skips a lot of occurrences - vba

I have a Workbook with 6 Sheets. I am walking through them with For Each. And the task is:
1) Walk though every cell with specified Range
2) If cell is not empty AND contains ONLY number THEN add to the end of the cell " мм". Otherwise SKIP this cell.
But in fact, script does it good only for first sheet (Worksheet). It does no changes to other sheets. I don't know why this happens. I think, that there is some error or mistake in the code, but I double-checked it and everything seems to be correct. Help me please :)
Sub SaveWorksheetsAsCsv()
Dim xWs As Worksheet
Dim xDir As String
Dim folder As FileDialog
Dim r As Range
Dim rr As Range
Dim rrrrrr As Range
Dim cell As Range
k = Cells(Rows.Count, "A").End(xlUp).Row
Set folder = Application.FileDialog(msoFileDialogFolderPicker)
If folder.Show <> -1 Then Exit Sub
xDir = folder.SelectedItems(1)
For Each xWs In Application.ActiveWorkbook.Worksheets
If xWs.Name Like "Worksheet" Then
Set r = Range("FA2:FA" & k)
For Each cell0 In r
If IsEmpty(cell0.Value) = False And IsNumeric(cell0.Value) = True Then
cell0.Value = cell0.Value & " мм"
End If
Next
'xWs.Columns(41).EntireColumn.Delete
End If
If xWs.Name Like "Worksheet 1" Then
Set rr = Range("AG2:AG" & k)
For Each cell1 In rr
If IsEmpty(cell1.Value) = False And IsNumeric(cell1.Value) Then
cell1.Value = cell1.Value & " мм"
End If
Next
'xWs.Columns(126).EntireColumn.Delete
End If
If xWs.Name Like "Worksheet 5" Then
Set rrrrrr = Range("FR2:FR" & k)
For Each cell5 In rrrrrr
If IsEmpty(cell5.Value) = False And IsNumeric(cell5.Value) Then
cell5.Value = cell5.Value & " мм"
End If
Next
End If
xWs.SaveAs xDir & "\" & xWs.Name, xlCSV, local:=True
Next
End Sub

These sets of statements need to be adjusted to correct sheet references. Current code will always look at active sheet and the range reference is not qualified.
Set r = Range("FA2:FA" & k)
Set r = xWs.Range("FA2:FA" & k)

You can shorten-up and utilize your code a lot.
First, your k = Cells(Rows.Count, "A").End(xlUp).Row trying to get the last row, needs to be inside the For Each xWs In Application.ActiveWorkbook.Worksheets , since the last row will be different for each worksheet.
Second, instead of multiple Ifs, you can use Select Case.
Third, there is no need to have 3 different objects for Range, like r, rr, and rrr. The same goes for cell0, cell1 and cell5, you can use just one r and cell.
The only thing different inside your If (my Select Case) is the range you set r. The rest, looping through r.Cells is the same for all 3 criterias, so you can take this part outside the loop, and have it only once.
Modifed Code
Option Explicit
Sub SaveWorksheetsAsCsv()
Dim xWs As Worksheet
Dim xDir As String
Dim folder As FileDialog
Dim r As Range
Dim cell As Range
Dim k As Long
Set folder = Application.FileDialog(msoFileDialogFolderPicker)
If folder.Show <> -1 Then Exit Sub
xDir = folder.SelectedItems(1)
For Each xWs In ThisWorkbook.Worksheets ' it's safer to use ThisWorkbook is you reffer to the worksheets inside the workbook which thid code resides
With xWs
' getting the last row needs to be inside the loop
k = .Cells(.rows.Count, "A").End(xlUp).Row
Set r = Nothing ' reset Range Object
Select Case .Name
Case "Worksheet"
Set r = .Range("FA2:FA" & k)
'xWs.Columns(41).EntireColumn.Delete
Case "Worksheet 1"
Set r = .Range("AG2:AG" & k)
'xWs.Columns(126).EntireColumn.Delete
Case "Worksheet 5"
Set r = .Range("FR2:FR" & k)
End Select
' check if r is not nothing (it passed one of the 3 Cases in the above select case)
If Not r Is Nothing Then
For Each cell In r
If IsEmpty(cell.Value) = False And IsNumeric(cell.Value) Then
cell.Value = cell.Value & " мм"
End If
Next cell
End If
.SaveAs xDir & "\" & .Name, xlCSV, Local:=True
End With
Next xWs
End Sub

Related

Check if there is empty Range in vba

I am trying to filter by a list of Condition from the Condition wb to use for the Order wb. I use a checkEmpty range in order to check if there are no matching value then I will clear the filter and start with the next condition. But my code doesn't work and the error is "Range of object_worksheet" failed.
I get the error because even there is no matching value (empty range), the code still jump to Else condition.
Here is my code:
Sub Order()
Dim start As Double
Dim strKeyWord As String
Dim myCount As Integer
Dim checkEmpty As Range
Dim lRow1 As Long
Dim wsOrder As Worksheet
Dim wsCondition As Worksheet
Dim wbOrder As Workbook
Dim wbCondition As Workbook
Dim OrderFile As String
Dim ConditionFile As String
'Open Order wb
OrderFile = Application.GetOpenFilename()
Set wbOrder = Workbooks.Open(OrderFile)
Set wsOrder = wbOrder.Worksheets(1)
'Open Condition wb
ConditionFile = Application.GetOpenFilename()
Set wbCondition = Workbooks.Open(ConditionFile)
Set wsCondition = wbCondition.Worksheets(1)
'using the CountA ws function (all non-blanks)
myCount = Application.CountA(wsCondition.Range("A:A")) - 1
start = 2
For I = 1 To myCount Step 1
strKeyWord = wsCondition.Range("A" & start)
wsOrder.Range("R:R").AutoFilter Field:=1, Criteria1:="=*" & strKeyWord & "*"
'lRow1 = WorksheetFunction.Max(wsOrder.Range("I65536").End(xlUp).Row)
Set checkEmpty = wsOrder.Range("I2:I100").SpecialCells(xlCellTypeVisible)
If checkEmpty Is Nothing Then
On Error Resume Next
wsOrder.ShowAllData
On Error GoTo 0
Else
wsOrder.Range("I2", Range("I" & Rows.Count).End(xlUp)).Copy
With wsCondition
.Cells(.Rows.Count, "B").End(xlUp).Offset(1).PasteSpecial
End With
End If
start = start + 1
Next I
End Sub
Thank you very much!
So the main issue is that you didn't specify a worksheet for Range("I" & Rows.Count).End(xlUp).
Using
wsOrder.Range("I2", Range("I" & wsOrder.Rows.Count).End(xlUp)).Copy
should fix that.
But also I would correct the For I loop because you never use I. But you don't need the start variable and can use I instead which is also auto incremented.
'using the CountA ws function (all non-blanks)
myCount = Application.CountA(wsCondition.Range("A:A")) 'removed the -1
'remove start=2 and replace start with I
For I = 2 To myCount Step 1
strKeyWord = wsCondition.Range("A" & I)
wsOrder.Range("R:R").AutoFilter Field:=1, Criteria1:="=*" & strKeyWord & "*"
'lRow1 = WorksheetFunction.Max(wsOrder.Range("I65536").End(xlUp).Row)
Set checkEmpty = wsOrder.Range("I2:I100").SpecialCells(xlCellTypeVisible)
If checkEmpty Is Nothing Then
On Error Resume Next
wsOrder.ShowAllData
On Error GoTo 0
Else
wsOrder.Range("I2", Range("I" & Rows.Count).End(xlUp)).Copy
With wsCondition
.Cells(.Rows.Count, "B").End(xlUp).Offset(1).PasteSpecial
End With
End If
Next I

How to print Unique values one by one in text file from column in excel using vba

My scenario is that
How to print Unique values one by one in text file from column in excel using vba
and also show value in msgbox one by one using vba
Please give me a suggestion
thanks in Advance
here's the "msgbox" part:
Sub main()
Dim cell As Range
For Each cell In Columns(1).SpecialCells(xlCellTypeConstants, xlTextValues) 'change column index to suit your need
If WorksheetFunction.CountIf(Range(Cells(1, cell.Column), cell), cell.Value) < 2 Then MsgBox cell.Value
Next
End Sub
I'll leave the "print to text file" part to you
Sub Unik()
Dim sh As Worksheet, sh2 As Worksheet, lr As Long, rng As Range
Set sh = Sheet1 'Edit sheet name
Dim strFile_Path As String
Dim i As Integer
Dim iCntr As Long
Close #1
Open "C:\Users\Downloads\ghds.txt" For Output As #1
lr = sh.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = sh.Range("A2:A" & lr)
rng.AdvancedFilter Action:=xlFilterInPlace, Unique:=True
sh.Rows(1).Hidden = True
For Each c In rng.SpecialCells(xlCellTypeVisible)
If Mystr = "" Then
Mystr = c.Value
Else
Mystr = Mystr & ", " & c.Value
Print #1, c.Value
End If
Next
Close #1
End Sub

vba copy corresponding values from another workbook?

I have two workbooks:
Planner
Column K Column AG
123 £100
246 £20
555 £80
Master
Column D Column R
123 £100
246 £20
555 £80
I am trying to copy the values from Planner, Column AG into Column R (Master) where my item numbers in Column D (Master) match with column K (Planner).
My code below produces no error and it is not producing any results - despite their being several matches.
Please can someone show me where i am going wrong?
For the avoidance of doubt, my workbook is definitely opening ok so is finding the file.
Code:
Sub PlannerOpen()
'Set Variables
Dim wb2 As Workbook
Dim i As Long
Dim j As Long
Dim lastRow As Long
Dim app As New Excel.Application
'Find Planner
If Len(FindDepotMemo) Then
'If Found Then Set Planner Reference.
app.Visible = False 'Visible is False by default, so this isn't necessary
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.EnableEvents = False
Set wb2 = Workbooks.Open(FindDepotMemo, ReadOnly:=True, UpdateLinks:=False)
'If We have our planner lets continue...
'With my workbook
With wb2.Worksheets(1)
lastRow = .Cells(.Rows.Count, "K").End(xlUp).Row
'Lets begin our data merge
j = 2
For i = 2 To lastRow
'If data meets criteria
'Check Planner For Turnover
If ThisWorkbook.Worksheets("Data").Range("D" & j).Value = .Range("K" & i).Value Then ' check if Item number matches
ThisWorkbook.Worksheets("Data").Range("R" & j).Value = .Range("AG" & i).Value
j = j + 1
End If
'Continue until all results found
Next i
End With
'All Done, Let's tidy up
'Close Workbooks
'wb2.Close SaveChanges:=False
'app.Quit
'Set app = Nothing
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True
End If
End Sub
Function FindDepotMemo() As String
Dim Path As String
Dim FindFirstFile As String
Path = "G:\BUYING\Food Specials\2. Planning\1. Planning\1. Planner\" & "8." & " " & Year(Date) & "\"
FindFirstFile = Dir$(Path & "*.xlsx")
While (FindFirstFile <> "")
If InStr(FindFirstFile, "Planner") > 0 Then
FindDepotMemo = Path & FindFirstFile
Exit Function
End If
FindFirstFile = Dir
Wend
End Function
Instead of having 2 For loops, just use the Application.Match to find matches between values in your 2 workbooks.
Use this code section below to replace with yours:
With wb2.Worksheets(1)
Dim MatchRow As Variant '<-- define variable to get the row number if Match is successful
lastRow = .Cells(.Rows.Count, "K").End(xlUp).Row
'Lets begin our data merge
For i = 2 To lastRow
' If data meets criteria
' Check Planner For Turnover
' Use Application.Match to find matching results between workbooks
If Not IsError(Application.Match(ThisWorkbook.Worksheets("Data").Range("D" & i).Value, .Range("K2:K" & lastorw), 0)) Then ' check if Match is successful
MatchRow = Application.Match(ThisWorkbook.Worksheets("Data").Range("D" & i).Value, .Range("K2:K" & lastorw), 0) ' <-- get the row number where the match was found
ThisWorkbook.Worksheets("Data").Range("R" & j).Value = .Range("AG" & MatchRow).Value
End If
'Continue until all results found
Next i
End With
you could refactor your code as follows:
Option Explicit
Sub PlannerOpen()
Dim dataRng As Range, cell As Range
Dim depotMemo As String
Dim iRow As Variant
If FindDepotMemo(depotMemo) Then '<--| if successfully found the wanted file
With ThisWorkbook.Worksheets("Data1") '<--| reference your "Master" workbook relevant worksheet
Set dataRng = .Range("D2", .Cells(.Rows.Count, "D").End(xlUp)) '<--| set its item numbers range
End With
With Workbooks.Open(depotMemo, ReadOnly:=True, UpdateLinks:=False).Worksheets(1) '<--| open depotMemo workbook and reference its first worksheet
For Each cell In .Range("K2", .Cells(.Rows.Count, "K").End(xlUp)) '<--| loop through referenced worksheet column "K" cells from row 2 down to last not empty one
iRow = Application.Match(cell.Value, dataRng, 0) '<--| try finding current depotMemo item number in Master item numbers range
If Not IsError(iRow) Then dataRng(iRow, 1).Offset(, 14).Value = cell.Offset(, 22) '<--| if found then grab depotMemo current item amount and place it in corresponding "master" data sheet column R
Next
.Parent.Close False
End With
End If
End Sub
Function FindDepotMemo(depotMemo As String) As Boolean
Dim Path As String
Dim FindFirstFile As String
Path = "G:\BUYING\Food Specials\2. Planning\1. Planning\1. Planner\" & "8." & " " & Year(Date) & "\"
FindFirstFile = Dir$(Path & "*.xlsx")
While (FindFirstFile <> "")
If InStr(FindFirstFile, "Planner") > 0 Then
FindDepotMemo = True
depotMemo = Path & FindFirstFile
Exit Function
End If
FindFirstFile = Dir
Wend
End Function

VBA - select specific sheets within workbook to loop through

I have an excel workbook with a variable number of sheets. At the moment I am looping through all sheets and therein a specific column to search for figures above a certain threshold. Column and threshold are determined by inputboxes that need to be filled in by the user. If the figure in the column, let's say column "J" and row 10 is above threshold, row 10 is copied and pasted in a new created "summary" sheet etc.
I am struggling at the moment with a specific selection of sheets. I don't always want to loop through all sheets but instead would like to have another inputbox or something else in which I can select specific sheets (STRG + "sheetx" "sheety" etc...) that are looped through?! Anyone an idea how I can accomplish that with my code? I know that I have to change my "for each" statement to substitute for the selected sheets but I don't know how to create the inputbox to select specific tabs...
Any help appreciated!
Option Explicit
Sub Test()
Dim column As String
Dim WS As Worksheet
Dim i As Long, j As Long, lastRow As Long
Dim sh As Worksheet
Dim sheetsList As Variant
Dim threshold As Long
Set WS = GetSheet("Summary", True)
threshold = Application.InputBox("Input threshold", Type:=1)
column = Application.InputBox("Currency Column", Type:=2)
j = 2
For Each sh In ActiveWorkbook.Sheets
If sh.Name <> "Summary" Then
lastRow = sh.Cells(sh.Rows.Count, "A").End(xlUp).Row
For i = 4 To lastRow
If sh.Range(column & i) > threshold Or sh.Range(column & i) < -threshold Then
sh.Range("a" & i & ":n" & i).Copy Destination:=WS.Range("A" & j)
WS.Range("N" & j) = sh.Name
j = j + 1
End If
Next i
End If
Next sh
WS.Columns("A:N").AutoFit
End Sub
Function GetSheet(shtName As String, Optional clearIt As Boolean = False) As Worksheet
On Error Resume Next
Set GetSheet = Worksheets(shtName)
If GetSheet Is Nothing Then
Set GetSheet = Sheets.Add(after:=Worksheets(Worksheets.Count))
GetSheet.Name = shtName
End If
If clearIt Then GetSheet.UsedRange.Clear
End Function
in the "NO-UserForm" mood you could use a combination of Dictionary object and the Application.InputBox() method when setting its Type parameter to 8 and have it accept range selections:
Function GetSheets() As Variant
Dim rng As Range
On Error Resume Next
With CreateObject("Scripting.Dictionary")
Do
Set rng = Nothing
Set rng = Application.InputBox(prompt:="Select any range in wanted Sheet", title:="Sheets selection", Type:=8)
.item(rng.Parent.Name) = rng.Address
Loop While Not rng Is Nothing
GetSheets = .keys
End With
End Function
this function gets the Parent sheet name out of each range selected by the user switching through sheets and stops when the user clicks the Cancel button or closes the InputBox
to be exploited by your "main" sub as follows:
Sub main()
Dim ws As Worksheet
For Each ws In Sheets(GetSheets) '<--| here you call GetSheets() Function and have user select sheets to loop through
MsgBox ws.Name
Next
End Sub
Agreed that a UserForm could offer a more appealing way to define it, however the InputBox approach isn't bad. The following code creates an InputBox that accepts a sheet range entry in the same way as a print dialog accepts page numbers, i.e. either explicit sheet numbers separated by commas (1, 3, 9) or a range separated with a hyphen (1-9).
This will look like a lot of code but it's got some error handling to prevent ugly failures. Your loop For Each sh In ActiveWorkbook.Sheets would be replaced by a loop like the example at the bottom of the code.
Sub sheetLoopInputBox()
Dim mySheetsArr2(999)
'Gather sheet range from inputbox:
mySheets = Replace(InputBox("Enter sheet numbers you wish to work on, e.g.:" & vbNewLine & vbNewLine & _
"1-3" & vbNewLine & _
"1,3,5,7,15", "Sheets", ""), " ", "")
If mySheets = "" Then Exit Sub 'user clicked cancel or entered a blank
'Remove spaces from string:
If InStr(mySheets, " ") Then mySheets = Replace(mySheets, " ", "")
If InStr(mySheets, ",") Then
'Comma separated values...
'Create array:
mySheetsArr1 = Split(mySheets, ",")
'Test if user entered numbers by trying to do maths, and create final array:
On Error Resume Next
For i = 0 To UBound(mySheetsArr1)
mySheetsArr2(i) = mySheetsArr1(i) * 1
If Err.Number <> 0 Then
Err.Clear
MsgBox "Error, did not understand sheets entry."
Exit Sub
End If
Next i
i = i - 1
ElseIf InStr(mySheets, "-") Then
'Hyphen separated range values...
'Check there's just one hyphen
If Len(mySheets) <> (Len(Replace(mySheets, "-", "")) + 1) Then
MsgBox "Error, did not understand sheets entry."
Exit Sub
End If
'Test if user entered numbers by trying to do maths:
On Error Resume Next
temp = Split(mySheets, "-")(0) * 1
temp = Split(mySheets, "-")(1) * 1
If Err.Number <> 0 Then
Err.Clear
MsgBox "Error, did not understand sheets entry."
Exit Sub
End If
On Error GoTo 0
'Create final array:
i = 0
i = i - 1
For j = Split(mySheets, "-")(0) * 1 To Split(mySheets, "-")(1) * 1
i = i + 1
mySheetsArr2(i) = j
Next j
End If
'A loop to do your work:
'(work through the sheet numbers stored in the array mySheetsArr2):
For j = 0 To i
'example1:
MsgBox mySheetsArr2(j)
'example2:
'Sheets(mySheetsArr2(j)).Cells(1, 1).Value = Now()
'Sheets(mySheetsArr2(j)).Columns("A:A").AutoFit
Next j
End Sub

Excel Vba: Creating loop that checks if the values in column A matches and copy all the rows to a new spreadsheet

I need to select all the rows in column A that have the same the value and paste them to a new spreadsheet named with the copied name.
In the example picture when I run macro and input value Banana I should get all the rows that contain banana in column A.
I found following vba code from the internet and tried to modify it to my needs but I'm stuck:
Sub LookForAllSameValues()
Dim LSearchRow As Integer
Dim LCopyToRow As Integer
On Error GoTo Err_Execute
'Start search in row 4
LSearchRow = 2
'Start copying data to row 2 in Sheet2 (row counter variable)
LCopyToRow = 2
Uname = InputBox("Test")
ActiveWorkbook.Worksheets.Add.Name = Uname
While Len(Range("A" & CStr(LSearchRow)).Value) > 0
'If value in column E = "Mail Box", copy entire row to Sheet2
If Range("A" & CStr(LSearchRow)).Value = Uname Then
'Select row in Sheet1 to copy
Rows(CStr(LSearchRow) & ":" & CStr(LSearchRow)).Select
Selection.Copy
'Paste row into Sheet2 in next row
Sheets(Uname).Select
Rows(CStr(LCopyToRow) & ":" & CStr(LCopyToRow)).Select
ActiveSheet.Paste
'Move counter to next row
LCopyToRow = LCopyToRow + 1
'Go back to Sheet1 to continue searching
Sheets("Sheet1").Select
End If
LSearchRow = LSearchRow + 1
Wend
'Position on cell A3
Application.CutCopyMode = False
Range("A3").Select
MsgBox "All matching data has been copied."
Exit Sub
Err_Execute:
MsgBox "An error occurred."
End Sub
This code almost works. It asks user to input string to search and then it creates a new worksheet named as this one. The problem lies in the loop, I debugged the code and for some reason it just skips copy paste loop
How do I get the loop working?
Output when the code is run:
I'm assuming you're testing this on the data shown above.
Your code states that LSearch Row = 2 and therefore your search will begin in cell A2. I'd therefore assume your loop is never executing because Len(Range("A2")) equals 0 (the cell is empty) and the loop immediately exits. This also means that if any cell in column A is empty the loop will end there even if there is more data below it.
Instead try using a For..Next loop as shown below which will run from row 2 to the last used row in the active sheet, regardless of the cell contents.
Public Sub FindAndCreateNew()
Dim strFind As String
Dim i As Long, j As Long
Dim wsFind As Worksheet
Dim wsPaste As Worksheet
'Get value to search for
strFind = InputBox("Test")
'Create object reference to the current worksheet
Set wsFind = ActiveSheet
'Create a new worksheet with object reference and then rename it
Set wsPaste = Worksheets.Add
wsPaste.Name = strFind
'Paste starting at row 2 in wsPaste
j = 2
'Start searching from row 2 of wsFind, continue to end of worksheet
For i = 2 To wsFind.UsedRange.Rows.Count
If wsFind.Range("A" & i) = strFind Then
'Copy row i of wsFind to row j of wsPaste then increment j
wsFind.Range(i & ":" & i).Copy Destination:=wsPaste.Range(j & ":" & j)
j = j + 1
End If
Next i
End Sub
P.S. It's also worth noting that the use of .Select is generally avoidable and it can slow the program down considerably as well as making it less readable. For example this:
'Select row in Sheet1 to copy
Rows(CStr(LSearchRow) & ":" & CStr(LSearchRow)).Select
Selection.Copy
Could be represented with just one statement as below:
'Select row in Sheet1 to copy
Rows(CStr(LSearchRow) & ":" & CStr(LSearchRow)).Copy
As commented, try this:
Sub test()
Dim sh1 As Worksheet, sh2 As Worksheet
Dim rng As Range
Dim uname As String
Set sh1 = Sheet1: uname = InputBox("Input")
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
If Len(uname) = 0 Then MsgBox "Invalid input": Exit Sub
Set sh2 = ThisWorkbook.Sheets.Add(after:= _
ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
On Error Resume Next
sh2.Name = uname: If Err.Number <> 0 Then MsgBox "Data already copied": _
sh2.Delete: Exit Sub
On Error GoTo 0
With sh1
.AutoFilterMode = False
Set rng = .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
rng.AutoFilter 1, uname
On Error Resume Next
rng.SpecialCells(xlCellTypeVisible).EntireRow.Copy sh2.Range("A1")
If Err.Number <> 0 Then MsgBox "Data not found" _
Else MsgBox "All matching data has been copied"
.AutoFilterMode = False
On Error GoTo 0
End With
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
End Sub