Format Date VBA - excel

i want to implement this recorded macro into a macro for my code, i succesfully transformed "E" row into general, and i want to change that date into Short Date format DD/MM/YYYY the macro i recorded is this one below:
Sub Macro2()
'
' Macro2 Macro
Range("L4").Select
ActiveCell.FormulaR1C1 = "=DATEVALUE(MID(RC[-7],1,10))"
Range("L4").Select
Selection.AutoFill Destination:=Range("L4:L4500"), Type:=xlFillDefault
Range("L4:L4500").Select
Selection.NumberFormat = "m/d/yyyy"
End Sub
I tried it by making the function into the L Column, if it is possible i would like to implement it in one column so all values change and then paste them into the E column
The whole E column is like this:
01-10-2019 52:59:76
02-10-2019 52:59:76
02-10-2019 52:59:76
05-10-2019 52:59:76
And i want them to change into
01/10/2019
02/10/2019
02/10/2019
05/10/2019
This the code i used to transform the whole E column data to the format of dd-mm-yyyy hh:mm:ss to correct the error of some data not changing into the correct format
With ActiveSheet.UsedRange.Columns("E").Cells
Columns("E").NumberFormat = "0"
Columns("E").NumberFormat = "General"
End With

If in '01-10-2019 52:59:76' first two digits means day, try please the next code:
Sub testDateFormat()
Dim lastRow As Long, sh As Worksheet, x As String, i As Long
Set sh = ActiveSheet 'use here your sheet, if not active one
lastRow = sh.Range("E" & sh.Rows.count).End(xlUp).Row
sh.Range("E1:E" & lastRow).NumberFormat = "dd/mm/yyyy"
For i = 2 To lastRow
If sh.Range("E" & i).Value <> Empty Then
If chkFind(CStr(sh.Range("E" & i).Value)) = True Then
x = CStr(sh.Range("E" & i).Value)
sh.Range("E" & i).Value = Format(DateSerial(Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(2), _
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(1), _
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(0)), "dd/mm/yyyy")
Else
Debug.Print "Unusual string on the row " & i
End If
End If
Next i
End Sub
Private Function chkFind(strVal As String) As Boolean
On Error Resume Next
If WorksheetFunction.Find(" ", strVal) = 11 Then
chkFind = True
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0
chkFind = False
End If
Else
chkFind = False
End If
On Error GoTo 0
End Function
If first digits represents month, then the last two array (split) elements must be vice versa:
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(0), _
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(0))
instead of
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(1), _
Split(left(x, WorksheetFunction.Find(" ", x) - 1), "-")(1))

Related

VBA Macro is ignoring nextBlankRow and duplicates

What I want the Macro to accomplish:
I want the user to be able to fill in data from E2 to E9 on the spreadsheet. When the user presses the "Add Car" button the macro is supposed to be executed. The makro then should take the handwritten data, copy everything from E2:E9 and put it into a table that starts at with C13 and spans over 7 columns, always putting the new set of data in the next free row. It is also supposed to check for duplicates and give an alert while not overwriting the original set of data
So my problem is, that I want the Macro I'm writing to take the information put into certain cells and then copy them into a table underneath.
I'm starting the Macro like this
Sub addData()
Dim lastrow As Long, nextBlankRow As Long
lastrow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlPrevious, _
MatchCase:=False).Row
nextBlankRow = lastrow + 1
Here I try to define how the Macro is supposed to find the last empty cell and also define lastrow and nextBlankRow.
After that I'm starting with a simple If statement to see if the person has at least something in E2 on the same sheet.
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
This works. When I'm not putting something into E2 I get the textbox with the alert.
Anyway if the IF-Statement is not triggered to exit the sub the Macro is given the instructions to get the information and put it in the table below
Cells(nextBlankRow, 3) = Range("E2")
Cells(nextBlankRow, 4) = Range("E3")
Cells(nextBlankRow, 5) = Range("E4")
Cells(nextBlankRow, 6) = Range("E5")
Cells(nextBlankRow, 7) = Range("E6")
Cells(nextBlankRow, 8) = Range("E7")
Cells(nextBlankRow, 9) = Range("E8")
Here seems to be a problem that probably relates to me failing to define variables correctly?
Because the Macro finds the right row but only overwrites into that row. So it ignores the fact that it "should" skip to the nextBlankrow which I defined earlier as
nextBlankRow = lastrow + 1
In addition to that I also have a line of code inplace which is supposed to check for duplicates
Dim p As Long, q As Long
p = 13
q = p + 1
Do While Cells(p, 3) <> ""
Do While Cells(q, 3) <> ""
If Cells(p, 3) = Cells(q, 3) And Cells(p, 4) = Cells(q, 4) Then
MsgBox "Datensatz schon vorhanden!"
Range(Cells(q, 3), Cells(q, 9)).ClearContents
Else
q = q + 1
End If
Loop
p = p + 1
q = p + 1
Loop
End Sub
Which always gives a false return. So even if the same set of Data is copied twice into the same row (as it does) it only "refreshes" the data and doesn't say "you're not allowed to do that".
I'm at a loss here.
Here's the full code for ease of use
Sub addData()
Dim lastrow As Long, nextBlankRow As Long
lastrow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlPrevious, _
MatchCase:=False).Row
nextBlankRow = lastrow + 1
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
Cells(nextBlankRow, 3) = Range("E2")
Cells(nextBlankRow, 4) = Range("E3")
Cells(nextBlankRow, 5) = Range("E4")
Cells(nextBlankRow, 6) = Range("E5")
Cells(nextBlankRow, 7) = Range("E6")
Cells(nextBlankRow, 8) = Range("E7")
Cells(nextBlankRow, 9) = Range("E8")
Dim p As Long, q As Long
p = 13
q = p + 1
Do While Cells(p, 3) <> ""
Do While Cells(q, 3) <> ""
If Cells(p, 3) = Cells(q, 3) And Cells(p, 4) = Cells(q, 4) Then
MsgBox "Datensatz schon vorhanden!"
Range(Cells(q, 3), Cells(q, 9)).ClearContents
Else
q = q + 1
End If
Loop
p = p + 1
q = p + 1
Loop
End Sub
```![enter image description here](https://i.stack.imgur.com/dJozM.jpg)![enter image description here](https://i.stack.imgur.com/Q90Ah.jpg)
Please, test the next code:
Sub copyRangeOnLastEmptyRow()
Dim sh As Worksheet, arr, lastERow As Long, matchCel As Range
Set sh = ActiveSheet
arr = sh.Range("E2:E9").value
lastERow = sh.Range("C" & sh.rows.Count).End(xlUp).row + 1
If lastERow < 13 Then lastERow = 13
'check if the range has not been alredy copied:
Set matchCel = sh.Range("C13:C" & lastERow - 1).Find(WHAT:=sh.Range("E2").value, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=False)
If Not matchCel Is Nothing Then
MsgBox sh.Range("E2").value & " has been found in cell " & matchCel.Address & "."
'bring up the data of the existing row:
sh.Range("E3:E9").value = Application.Transpose(sh.Range(matchCel.Offset(0, 1), matchCel.Offset(0, 7)).value)
Exit Sub
End If
sh.Range("C" & lastERow).Resize(1, UBound(arr)).value = Application.Transpose(arr)
sh.Range("E2:E9").ClearContents
End Sub

Compare two cells and show output with symbol

I've been asked to create a macro that compare two numbers in two cells and then it should write a third column that says for example: L6 is less than M6 (any image of a down arrow)
I tried to record this macro:
Sub Macro20()
Range("N2").Select
ActiveCell.FormulaR1C1 = _
"=IF(RC[-2]=RC[-1],""L and M are equal"",IF(RC[-2]>RC[-1],""L is greater than M (UP ARROW) "",""L is less than M (DOWN ARROW)""))"
Range("N2").Select
Selection.AutoFill Destination:=Range("N2:N" & Range("L" & Rows.Count).End(xlUp).Row)
Range(Selection, Selection.End(xlDown)).Select
End Sub
and this is the output:
This is just an example, the whole code should be used to a large amount of data soon, anyway there are some errors must be avoided.
The code into the cell must not be shown (see the blue arrow into the picture), it should display only the value.
How can I fetch an arrow image instead of the string: L is greater than M (UP ARROW)?
Can you help me in doing a better code than this?
Here is a simple solution which enters the formula in the entire range without looping.
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Find last row in column L
lRow = .Range("L" & .Rows.Count).End(xlUp).Row
'~~> Insert the formula in Col N. Change as applicable
With .Range("N1:N" & lRow)
.Formula = "=IF(L1=M1,""L and M are equal"",IF(L1>M1,""L is greater than M " & _
ChrW(&H2191) & _
""", ""L is less than M " & _
ChrW(&H2193) & _
"""))"
'~~> Optional - Convert formula to values
.Value = .Value
End With
End With
End Sub
Screenshot
Note:
To insert Up arrow, you can use ChrW(&H2191) and for down arrow you can use ChrW(&H2193)
If you want to put the formula from the 2nd row then it will be
'~~> Insert the formula in Col N. Change as applicable
With .Range("N2:N" & lRow)
.Formula = "=IF(L2=M2,""L and M are equal"",IF(L2>M2,""L is greater than M " & _
ChrW(&H2191) & _
""", ""L is less than M " & _
ChrW(&H2193) & _
"""))"
'~~> Optional - Convert formula to values
.Value = .Value
End With
Similarly for a different row, you will have to adjust accordingly.
EDIT
do you think is possible to use a arrow text already formatted? For example a red one (or whatever color) with a specific size? And then put this inside your vba code? – Alex D. 4 hours ago
Yes it is possible. In this case you can use Worksheet_Change event to handle changes in column L and column M to populate column N
I have commented the code below. If you still have problems understanding it then feel free to ask. The below code goes in the sheet code area. You can change the symbol attributes (Style, Color and Size) right at the top of the code.
Code
Option Explicit
'~~> Change the symbol attributes here
Const Font_Style As String = "Bold"
Const Font_Size As Long = 15
Const Font_Color As Long = -16776961 '(Red)
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
Dim r As Variant
'~~> Check if the change happened in the relevant column
If Not Intersect(Target, Me.Range("L:M")) Is Nothing Then
For Each r In Target.Rows
'~~> If even one cell is empty then clear out N cell
If Len(Trim(Range("L" & r.Row).Value2)) = 0 Or _
Len(Trim(Range("M" & r.Row).Value2)) = 0 Then
Range("N" & r.Row).ClearContents
'~~> Check if L = M
ElseIf Range("L" & r.Row) = Range("M" & r.Row) Then
Range("N" & r.Row).Value = "L and M are equal"
'~~> Check if L > M
ElseIf Range("L" & r.Row) > Range("M" & r.Row) Then
With Range("N" & r.Row)
.Value = "L is greater than M " & ChrW(&H2191)
'~~> Format the symbol which is at 21st position
With .Characters(Start:=21, Length:=1).Font
.FontStyle = Font_Style
.Size = Font_Size
.Color = Font_Color
End With
End With
'~~> L < M
Else
With Range("N" & r.Row)
.Value = "L is less than M " & ChrW(&H2193)
'~~> Format the symbol which is at 18th position
With .Characters(Start:=18, Length:=1).Font
.FontStyle = Font_Style
.Size = Font_Size
.Color = Font_Color
End With
End With
End If
Next r
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
In action
Here is an alternative:
Sub alex()
Dim i As Long, LastRow As Long
Dim L, M, txt As String
LastRow = Cells(Rows.Count, "L").End(xlUp).Row
For i = 2 To LastRow
L = Cells(i, "L").Value
M = Cells(i, "M").Value
If L = M Then
txt = "they are equal"
ElseIf L > M Then
txt = "L is greater"
Else
txt = "M is greater"
End If
Cells(i, "N") = txt
Next i
End Sub
You can speed this up a little by bring all the column L and M data into VBA arrays and doing the comparisons within VBA.
To get arrows rather than text, use:
Sub alex()
Dim i As Long, LastRow As Long
Dim L, M, txt As String
LastRow = Cells(Rows.Count, "L").End(xlUp).Row
For i = 2 To LastRow
L = Cells(i, "L").Value
M = Cells(i, "M").Value
If L = M Then
txt = "n"
ElseIf L > M Then
txt = "h"
Else
txt = "i"
End If
Cells(i, "N") = txt
Next i
End Sub
and format the results cells in column N to use the Wingdings 3 font

CountIf results in "0"

I need help for one part of my complete code. The code below should count the rows that meet the defined conditions for every month. The problem is that I only get "0" as result. I cant figure out why? Is it maybe because of my date format? My source wb contains dates in a format like "01.01.2019".
The code should also only count rows from the first of any month. Meaning: 01.01., 01.02.,01.03. etc.
My master workbook(wb) has an user form with file explorer function, where I can choose my source workbook(wbSource) and apply the macro on it. As seen in the complete code below.
Any help would be appreciated.
Dim x As Long
For x = 1 To 12
ws.Cells(8, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), "<=" & 50)
ws.Cells(9, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), ">" & 50, wsSource.Columns(30), "<=" & 100)
ws.Cells(10, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), ">" & 100)
Next x
Complete Code
Private Sub CommandButton2_Click() ' update averages
Const YEAR = 2019
' open source workbook
Dim fname As String, wbSource As Workbook, wsSource As Worksheet
fname = Me.TextBox1.Text
If Len(fname) = 0 Then
MsgBox "No file selected", vbCritical, "Error"
Exit Sub
End If
Set wbSource = Workbooks.Open(fname, False, True) ' no link update, read only
Set wsSource = wbSource.Sheets("Sheet1") ' change to suit
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Table 2") '
' scan down source workbook calc average
Dim iRow As Long, lastRow As Long
Dim sMth As String, iMth As Long
Dim count(12) As Long, sum(12) As Long
lastRow = wsSource.Cells(Rows.count, 1).End(xlUp).Row
For iRow = 1 To lastRow
If IsDate(wsSource.Cells(iRow, 8)) _
And IsNumeric(wsSource.Cells(iRow, 30)) Then
iMth = Month(wsSource.Cells(iRow, 8)) ' col H
sum(iMth) = sum(iMth) + wsSource.Cells(iRow, 30) ' Col AD
count(iMth) = count(iMth) + 1 '
End If
Next
' counting the rows
Dim x As Long
For x = 1 To 12
ws.Cells(8, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), "<=" & 50)
ws.Cells(9, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), ">" & 50, wsSource.Columns(30), "<=" & 100)
ws.Cells(10, 1 + x) = _
Application.WorksheetFunction.CountIfs(wsSource.Columns(8), Format(x, "00") & "." & MonthName(x), _
wsSource.Columns(30), ">" & 100)
Next x
' close source worbook no save
wbSource.Close False
' update Table 2 with averages
With ws.Range("A3")
For iMth = 1 To 12
.Offset(0, iMth - 1) = MonthName(iMth) & " " & YEAR
If count(iMth) > 0 Then
.Offset(1, iMth - 1) = sum(iMth) / count(iMth)
.Offset(1, iMth - 1).NumberFormat = "0.0"
End If
Next
End With
Dim msg As String
msg = iRow - 1 & " rows scanned in " & TextBox1.Text
MsgBox msg, vbInformation, "Table 2 updated"
End Sub
Result-Sheet in Wb - as you see there are only zeroes displayed in the number of rows table
Example of wb.Source - Columns H(8) and AD(30) are the only relevant one for the ounting of the rows. In the example belows the last row should not be counted as the date does not match the condition ( its not the first of the month).
Yes, the problem is from the way you handle the dates. Your data has true dates. They aren't random numbers but a count of days since Jan 1, 1900. They uniquely identify every day for the past 120 years, and the system will continue to identify every day for the next 120 years, too. I believe the code below will do, more or less, what you intended to do.
Sub WriteCountIf()
' 025
' The YearCell must contain a year number like 2019.
Const YearCell As String = "A1" 'change to suit
Dim DateRng As Range ' the range to search for dates in
Dim DaysRng As Range ' the range to search for days in
Dim StartDate As Date ' first day to include in count
Dim EndDate As Date ' last day to include in count
Dim C As Long ' the column to write to (= month)
With ActiveSheet ' better to define the sheet by name
' make sure your result range doesn't overlap the search ranges
Set DateRng = .Range(.Cells(11, 8), .Cells(.Rows.Count, 8).End(xlUp))
Set DaysRng = .Range(.Cells(11, 30), .Cells(.Rows.Count, 30).End(xlUp))
For C = 1 To 12
StartDate = DateSerial(.Range(YearCell).Value, C, 1)
EndDate = DateSerial(Year(StartDate), C + 1, 0)
.Cells(8, 1 + C).Value = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, "<=" & 50)
.Cells(9, 1 + C).Value = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, ">" & 50, _
DaysRng, "<=" & 100)
.Cells(10, 1 + C).Value = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, ">" & 100)
Next C
End With
End Sub
It seems that your data only show day and month. That's a matter of display (cell formatting). The underlying dates include the year. Therefore you need a year when you search them. I have added a year in A1. You can move that cell to anywhere or hard-program a year into the code.
Your definition of search ranges, just by columns, is insufficient, and it overlaps with the cells into which you want to write the counts. I have assumed a start row and my code finds the end of each column. Please try it out.
In the code below the counting procedure is converted into a function that returns its result as an array. This is called by the main procedure and written to the worksheet. I couldn't test the code en total but the part described above works as advertised.
Private Sub CommandButton2_Click() ' update averages
' "Year" is the name of a VBA function.
' To use the same word here as a variable name is asking for trouble.
Const Year As Integer = 2019
Dim wbSource As Workbook, wsSource As Worksheet
Dim Wb As Workbook, Ws As Worksheet
Dim fname As String
fname = Me.TextBox1.Text
If Len(fname) = 0 Then
MsgBox "No file selected", vbCritical, "Error"
Exit Sub
End If
Set Wb = ThisWorkbook
Set Ws = Wb.Sheets("Table 2") '
' open source workbook
Set wbSource = Workbooks.Open(fname, False, True) ' no link update, read only
Set wsSource = wbSource.Sheets("Sheet1") ' change to suit
Dim iRow As Long, lastRow As Long
Dim sMth As String, iMth As Long
Dim count(12) As Long, sum(12) As Long ' Confusing: Count is a VBA method
' scan down source workbook calc average
' observe how .Rows.count is written with lower case c
' because of your declaration of "count" in that way
lastRow = wsSource.Cells(wsSource.Rows.count, 1).End(xlUp).Row
For iRow = 1 To lastRow
If IsDate(wsSource.Cells(iRow, 8)) And _
IsNumeric(wsSource.Cells(iRow, 30)) Then
iMth = Month(wsSource.Cells(iRow, 8)) ' col H
sum(iMth) = sum(iMth) + wsSource.Cells(iRow, 30) ' Col AD
count(iMth) = count(iMth) + 1
End If
Next
' counting the rows
Ws.Cells(8, 2).Resize(3, 12).Value = RowsCount(Year, wsSource)
' close source worbook no save
wbSource.Close False
' update Table 2 with averages
With Ws.Range("A3")
For iMth = 1 To 12
.Offset(0, iMth - 1) = MonthName(iMth) & " " & Year
If count(iMth) > 0 Then
.Offset(1, iMth - 1) = sum(iMth) / count(iMth)
.Offset(1, iMth - 1).NumberFormat = "0.0"
End If
Next
End With
Dim msg As String
msg = iRow - 1 & " rows scanned in " & TextBox1.Text
MsgBox msg, vbInformation, "Table 2 updated"
End Sub
Private Function RowsCount(ByVal RefYear As Integer, _
Ws As Worksheet) As Variant
' 025
Dim Fun As Variant ' Function return value
Dim DateRng As Range ' the range to search for dates in
Dim DaysRng As Range ' the range to search for days in
Dim StartDate As Date ' first day to include in count
Dim EndDate As Date ' last day to include in count
Dim C As Long ' the column to write to (= month)
With Ws
' make sure your result range doesn't overlap the search ranges
Set DateRng = .Range(.Cells(11, 8), .Cells(.Rows.count, 8).End(xlUp))
Set DaysRng = .Range(.Cells(11, 30), .Cells(.Rows.count, 30).End(xlUp))
End With
ReDim Fun(1 To 3, 1 To 12)
For C = 1 To 12
StartDate = DateSerial(RefYear, C, 1)
EndDate = DateSerial(Year(StartDate), C + 1, 0)
Fun(1, C) = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, "<=" & 50)
Fun(2, C) = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, ">" & 50, _
DaysRng, "<=" & 100)
Fun(3, C) = Application.WorksheetFunction. _
CountIfs(DateRng, ">=" & StartDate, _
DateRng, "<=" & EndDate, _
DaysRng, ">" & 100)
Next C
RowsCount = Fun
End Function
Capitalization is very important in VBA. As the example of your use of the Count word demonstrates VBA will try to adapt to your style. Therefore, if you have no system you will destroy VBA's. The theory I follow is to use caps and smalls on all variable names. Then I type using only lower case. VBA will correct my typing and this serves as a spell checker, helping to avoid typos. There are a few exceptions to this but that's the general rule I follow.
It's the same with the choice of variable names. Generally I avoid using words VBA uses. That prevents confusion for me (never mind VBA). But if I do use VBA's words I follow the existing capitalization.

Excel VBA Dynamic filters

I am trying to create dynamic filters based on varying conditions and different criteria. Suppose the user is providing some data like
Sal>100 and sal<1000 and not equal to 500
I am dynamically able to create the string with all the criteria and values and store that in a variable.
Here is the example:
Filter_con has the following value
Criteria1:=">10", Operator:=xlAnd, Criteria2:="<100000000",Operator:=xlFilterValues
When I am trying to execute the code
Selection.AutoFilter Field:=235, Filter_con
I am getting the error:
Run time error: 1004
AutoFilter method of range class failed.
Here is the code
t_lastrow = Cells(Rows.Count, 1).End(xlUp).Row
Range("A3:XFD" & t_lastrow).Select
If (ActiveSheet.AutoFilterMode And ActiveSheet.FilterMode) Or
ActiveSheet.FilterMode Then
Selection.ShowAllData
End If
Filter_Con=">10","<100000000"
Filter_numric_data = Split(Replace(Filter_Con, Chr(34), ""), ",")
UBU = UBound(Filter_numric_data)
Filter_Con = ""
For i__ = 0 To UBU
If i__ <> UBU Then
MsgBox (Filter_numric_data(i__))
Filter_Con = Filter_Con & " Criteria" & i__ + 1 & ":=" &
Filter_numric_data(i__) & ", Operator:=xlAnd,"
Else
Filter_Con = Filter_Con & " Criteria" & i__ + 1 & ":=" &
Filter_numric_data(i__)
End If
Next
Range("A3:XFD" & t_lastrow).Select
Selection.AutoFilter Field:=Filter_Field & "," & Filter_Con
Create a dictionary of all values to be shown. Use the dictionary items as the criteria1 with xlfiltervalues.
Option Explicit
Sub sdfgh()
Dim vals As Variant, i As Long, dict As Object
Set dict = CreateObject("scripting.dictionary")
With Worksheets("sheet1")
If .AutoFilterMode Then .AutoFilterMode = False
vals = .Range(.Cells(2, "IA"), .Cells(.Rows.Count, "IA").End(xlUp)).Value2
For i = LBound(vals, 1) To UBound(vals, 1)
Select Case True
Case vals(i, 1) > 100 And vals(i, 1) < 1000 And vals(i, 1) <> 500
'xlFilterValues requires text
dict.Item(vals(i, 1)) = CStr(vals(i, 1))
Case Else
'do nothing
End Select
Next i
With .Cells(1, "A").CurrentRegion
.AutoFilter field:=235, Criteria1:=dict.items, Operator:=xlFilterValues
End With
End With
End Sub

SaveAs failing despite not changing the code

I have a function in a larger macro that helps reformat and clean up some data from a specific measurement file so that it can be used by the rest of the macro. I recently needed to update some of the data clean-up & reformatting that the function does based on guidance from the department that uses the macro. Those changes work fine, but now the .SaveAs is failing on Error 1004 "SaveAs method of object '_Workbook' failed".
I compared the old & new versions in a text comparison program (UltraCompare) and the changes definitely shouldn't impact the SaveAs. If I stop the macro at the point just before SaveAs and manually save that works successfully, so nothing in the file itself is blocking the save, nor is it a permissions change I didn't know about (which is extra not likely since the test folder is a child of my Desktop).
To go through some of the answers I've seen to other questions on this error
I don't use ActiveWorkbook to save, I create the workbook when setting a workbook variable
I don't use a date in the save as file name
There are no hyperlinks in the workbook
Excel doesn't throw a prompt, and alerts are left on prior to the SaveAs line
As mentioned above, the save is to a folder off my Desktop, so network drive mapping involved
Some additional things I've tried:
During debug, creating a new variable immediately prior to the .SaveAs line & populating it with a new file name in the same folder, and using that in the .SaveAs in place of the replace
Again with a new variable prior to .SaveAs that specifies a different folder
Specifying FileFormat:=51
All that said, here's the code, if anyone has ideas I'm game:
Function MergeCDC(sw As StatWin, fpath As String, BadDateRef As Range, Optional FromComb As Boolean = False) As Boolean
'StatWin is a custom form with a text box for printing status text to the user & a progress bar. fpath is the full file path of the file to be used as a string (folder path & file name including file extension)
'BadDateRef is a cell in the workbook that holds this function that holds the date 1/1/1900 which is used by the file being processed to indicate no date (i.e. the field should be null, but the DBAs
'decided to be weird so we have to deal with it)
'FromComb is a way to know if this function was called by a specific other function, so that run time tracking can be handled correctly
'Check if we're blocked on CDC (this prevents the function from trying to run again if it's called a second (or greater) time after failing)
If sw.CDCBlock Then
MergeCDC = False
Exit Function
End If 'else continue
Dim src As Workbook
Set src = Workbooks.Open(fpath) 'No need to check if the CDC workbook is present as that was done prior to this function being invoked
Dim ry As Worksheet
Dim ytd As Worksheet
Dim m As Workbook
Set m = Workbooks.Add
Dim ms As Worksheet
Set ms = m.Worksheets(1)
Dim ret As Boolean
ret = False
Dim c As Long
Dim r As Long
Dim ryc As Long
Dim temp() As Long
Dim msc As Long
Dim z As Integer
Dim yfnd As Boolean
Dim rfnd As Boolean
'Update the RunStat sheet such that we track CDC data merge as it's own item
If FromComb Then
sw.RStat.Range("A" & sw.StatRow + 2).Value = sw.RStat.Range("A" & sw.StatRow + 1).Value
sw.RStat.Range("B" & sw.StatRow + 2).Value = sw.RStat.Range("B" & sw.StatRow + 1).Value 'Bump start time for combined list being created
sw.RStat.Range("A" & sw.StatRow + 1).Value = sw.RStat.Range("A" & sw.StatRow).Value 'bump start for creation of combined source file
sw.RStat.Range("B" & sw.StatRow + 1).Value = sw.RStat.Range("B" & sw.StatRow).Value
Else
sw.RStat.Range("A" & sw.StatRow + 1).Value = sw.RStat.Range("A" & sw.StatRow).Value 'bump start for creation of CDC list
sw.RStat.Range("B" & sw.StatRow + 1).Value = sw.RStat.Range("B" & sw.StatRow).Value
End If
sw.RStat.Range("A" & sw.StatRow).Value = "CDC Merge"
sw.RStat.Range("B" & sw.StatRow).Value = Now()
'Determine which sheet is which
z = 1
yfnd = True
rfnd = True
Do While z <= src.Worksheets.Count And (yfnd Or rfnd)
If InStr(1, UCase(src.Worksheets(z).Name), "YTD") > 0 Then
yfnd = False
Set ytd = src.Worksheets(z)
ElseIf InStr(1, UCase(src.Worksheets(z).Name), "RY") > 0 Then
rfnd = False
Set ry = src.Worksheets(z)
End If
z = z + 1
Loop
'Check we found both sheets
If rfnd Or yfnd Then
Call Err("Unable to locate the RY and/or YTD worksheets in the Unedited CDC file. Please update the file such that the YTD worksheet includes 'YTD' in its name, and the RY" _
& " worksheet includes 'RY' in its name. This error prevents any list utilizing CDC data from being completed.", sw)
MergeCDC = False
sw.CDCBlock = True
Exit Function
End If 'else continue as normal
'Prep the two worksheets
temp = CDCPrep(ry, True, sw)
ryc = temp(0)
r = temp(1) 'CDCPrep returns the first BLANK row so we will use r as the row to paste to when pasting YTD data
'Prep of RY successful?
If temp(0) <> -1 Then
temp = CDCPrep(ytd, False, sw)
Else
'Close the new workbook without saving
m.Close SaveChanges:=False
End If
'Continue?
If temp(0) <> -1 Then
'Copy the entirety of Rolling Year data
ry.Range("A1:" & ColNumToStr(ryc) & r - 1).Copy
ms.Range("A1").PasteSpecial xlPasteAll
'Start merging in the YTD data. Since we can't assume the columns are in the same order we'll start iterating through the RY columns and copying one column at a time from YTD
c = 0
Do While ms.Range("A1").Offset(0, c).Value <> ""
'Find the matching column in YTD
msc = 0
Dim fnd As Boolean
fnd = False
Do While ytd.Range("A1").Offset(0, msc).Value <> "" And fnd = False
If ytd.Range("A1").Offset(0, msc).Value = ms.Range("A1").Offset(0, c).Value Then
'Found the column. Copy it's data
fnd = True
ytd.Range(ColNumToStr(msc + 1) & "2:" & ColNumToStr(msc + 1) & temp(1)).Copy
Else
msc = msc + 1
End If
Loop
'Did we find a match?
If fnd Then
'Paste the data
ms.Range("A" & r).Offset(0, c).PasteSpecial xlPasteAll
Else
Call Err("Unable to locate the " & ms.Range("A1").Offset(0, c).Value & " column in the Yr To Date data. The list will be generated, but will be missing these values for items found only" _
& " in the Yr To Date data.", sw)
End If
c = c + 1
Loop
'Get the last row of data so we can sort the merged data
r = r + temp(1)
'Check that is the last row
Do While ms.Range("A" & r).Value <> "" And r < 600000 'ridiculously high value, but serves as a fail-safe to keep from hitting end of sheet and not having found data end
r = r + 1
Loop
'Sort the data and remove duplicates according to the current month (Jan - Jun: RY rows preferred to YTD; Jul - Dec: YTD preferred)
If Month(sw.CurDate) < 7 Then
'RY preferred
ms.Sort.SortFields.Clear
ms.Sort.SortFields.Add Key:=Range _
("A2:A" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
ms.Sort.SortFields.Add Key:=Range _
("B2:B" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ms.Sort
.SetRange Range("A1:" & ColNumToStr(c + 1) & r + temp(1))
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Else
'YTD preferred
ms.Sort.SortFields.Clear
ms.Sort.SortFields.Add Key:=Range _
("A2:A" & r), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
ms.Sort.SortFields.Add Key:=Range _
("B2:B" & r), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:= _
xlSortNormal
With ms.Sort
.SetRange Range("A1:" & ColNumToStr(c + 1) & r + temp(1))
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End If
ms.Range("A1:" & ColNumToStr(c + 1) & r + temp(1)).RemoveDuplicates Columns:=1, Header:=xlYes
'Delete the MergeKey & Source columns
ms.Range("A:B").Delete Shift:=xlLeft
'In order to be processed correctly by other functions later certain target values (Last Test Date, Last Test Value) need to be inserted as new SubMeasures (i.e. new rows)
Dim i As Long
Dim ik As String
Dim sm As String
Dim nc As String
Dim ltd As String
Dim ltv As String
Dim td As String
i = 0
fnd = True
'To add the rows we need to be able to tell when we're on the first row of data for a particular item. Meaning we need to know the column holding ItemKey
Do While ms.Range("A1").Offset(0, i).Value <> "" And fnd
Select Case LCase(ms.Range("A1").Offset(0, i).Value)
Case "itemkey"
mk = ColNumToStr(i + 1)
Case "submeasure"
sm = ColNumToStr(i + 1)
Case "numercnt"
nc = ColNumToStr(i + 1)
Case "date1"
ltd = ColNumToStr(i + 1)
Case "last_val"
ltv = ColNumToStr(i + 1)
Case "terminationdate"
td = ColNumToStr(i + 1)
End Select
i = i + 1
If sm <> "" And ik <> "" And td <> "" And ltd <> "" And nc <> "" And ltv <> "" Then
fnd = False
End If
Loop
If fnd Then
'Couldn't find the needed columns. Report the error
Call Err("Unable to locate the one or more of the following columns in the MergedCDC file: ItemKey, SubMeasure, NumerCnt, TerminationDate, Last Test Date, Last Test Value. This will prevent adding" _
& " rows for Last Test Value & Last Test Date, which will in turn mean those columns will not be correctly populated in any list based on CDC data. All other values from" _
& " the CDC data should be correct though.", sw)
Else
'Add the rows
Dim PM As String
i = 2
Do While ms.Range(mk & i).Value <> ""
If InStr(1, PM, "|" & ms.Range(mk & i).Value & "|") = 0 Then
'First row for this item set all Term Date values are set to the MAX Term Date value for the item. Also determine if they're non-compliant on any measure
Dim y As Integer
Dim tdv As Date
Dim ncv As Integer
y = 0
tdv = DateSerial(1900, 1, 1)
ncv = 1 'Start # 1 so that if any row is non-compliant we can change ncv then (as opposed to having to make sure ALL rows are compliant before setting ncv to 1)
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
If ms.Range(td & i + y).Value > tdv Then
tdv = ms.Range(td & i + y).Value
End If 'else the term date is older than tdv, and we want to standardize to the max term date, so leave tdv alone
If ms.Range(nc & i + y).Value < ncv Then
ncv = 0
ElseIf ms.Range(sm & i + y).Value = "Tested" Then
'Check if the Test Value = 0 and if the Last Test Date is valid
If (ms.Range(ltd & i + y).Value = DateSerial(1900, 1, 1) Or ms.Range(ltd & i + y).Value = "" Or ms.Range(ltd & i + y).Value = BadDateRef.Value) _
And ms.Range(lbg & i + y).Value = 0 Then
'The value is 0 and the date isn't valid, that means the item wasn't actually tested (in effect if not actuality). Set this row to not tested & update ncv
ms.Range(nc & i + y).Value = 0
ncv = 0
End If 'else the item was tested, the compliance value stays the same, which means ncv doesn't need changed
End If 'Else row indicates item is compliant, which is the default, so no action needed
y = y + 1
Loop
'Replace Term Date values that aren't TDV with TDV (technically, we also replace the row that set TDV, but with the same value)
If tdv <> DateSerial(1900, 1, 1) Then
y = 0
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
ms.Range(td & i + y).Value = tdv
y = y + 1
Loop
Else
'No actual date found for TDV, just clear the cells setting the format to General so that Excel doesn't re-fill in 1/1/1900
y = 0
Do While ms.Range(mk & i + y).Value = ms.Range(mk & i).Value
ms.Range(td & i + y).NumberFormat = "General"
ms.Range(td & i + y).ClearContents
y = y + 1
Loop
End If
'Copy the current row & insert two copies of it below the current row
ms.Range(i & ":" & i).Copy
ms.Range(i + 1 & ":" & i + 1).Insert Shift:=xlDown
ms.Range(i & ":" & i).Copy
ms.Range(i + 1 & ":" & i + 1).Insert Shift:=xlDown
'Change the SubMeasure cells appropriately
ms.Range(sm & i + 1).Value = "Last Test Date"
ms.Range(sm & i + 2).Value = "Last Test Val"
'Set the compliance cnt value. If the item's last value is 0 AND there is no Last Test Date value, the numercnt value for the two added rows should be 0 so that date & value
' appear (as even though they're compliant, they probably shouldn't be marked as such due to lack of proof). If the value is non-0 then set based on ncv
If ms.Range(lbg & i).Value = 0 & ms.Range(ltd & i + y).Value = "" Then
ms.Range(nc & i + 1).Value = 0
ms.Range(nc & i + 2).Value = 0
Else
ms.Range(nc & i + 1).Value = ncv
ms.Range(nc & i + 2).Value = ncv
End If
'Add the item to PM, a delimited string of ItemKeys for processed items that lets us check if we've already seen a row for this item
PM = PM & "|" & ms.Range(mk & i).Value & "|"
'Add 2 to i (this way the additional incrementing of i below results in us looking at row i + 3, which was the row that had been immediately below row i before we added the two new rows)
i = i + 2
End If 'else proceed to the next row, which happens anyway
i = i + 1
Loop
End If
'Clear out compliant rows so that MergedCDC processes through MFPRocessor (a seperate function that we're setting up the CDC data to go through) like any other source file
'(submeasure present = item non-compliant on measure)
i = 2
Do While ms.Range(mk & i).Value <> ""
If ms.Range(nc & i).Value = 1 Then
ms.Range(i & ":" & i).Delete Shift:=xlUp
Else
i = i + 1
End If
Loop
'Remove 1/1/1900 values from Last Test Date & Term Date
i = 2
Do While ms.Range(mk & i).Value <> ""
If ms.Range(ltd & i).Value = "1/1/1900" Or ms.Range(ltd & i).Value = BadDateRef.Value Then
ms.Range(ltd & i).NumberFormat = "General"
ms.Range(ltd & i).ClearContents
End If
If ms.Range(td & i).Value = "1/1/1900" Or ms.Range(td & i).Value = BadDateRef.Value Then
ms.Range(td & i).NumberFormat = "General"
ms.Range(td & i).ClearContents
End If
i = i + 1
Loop
ret = True
'Save the workbook
m.SaveAs (Replace(fpath, "CDC", "MergedCDC")) 'This code HAD worked, despite none of the changes being anything that should impact this line, this line
Application.DisplayAlerts = False
m.Close SaveChanges:=False
Application.DisplayAlerts = True
Else
'Close the new workbook without saving
m.Close SaveChanges:=False
End If
'Close the original CDC workbook
Application.DisplayAlerts = False
src.Close
Application.DisplayAlerts = True
'Capture completion of CDC merge
sw.RStat.Range("C" & sw.StatRow).Value = Now()
sw.StatRow = sw.StatRow + 1
MergeCDC = ret
End Function
If you haven't changed your code then here's a few things to check which could be causing the error:
Workbook object is out of context - ensure that you are using only one instance of Excel, if your data and workbook are in different instances then they wont be able to reach each other. When your code breaks at the error, add the workbook to your watch list to see if it reachable.
Filepath is unreachable - when the code breaks at this error, take the value of Replace(fpath, "CDC", "MergedCDC") without the filename at the end, and paste it into windows explorer and check that it is reachable.

Resources