Excel VBA script not working when grouping multiple levels - excel

I have an excel document that runs a VBA script that I use user forms to input data. The script works fine, except for the grouping. There are 2 groups. The first is at the Customer Name, which works fine. The second is at the Effort Name, which does not. It groups the effort, but when grouped it still displays the last row. The developer I hired to write the script said that this error appears to be a bug in Excel or for some reason by design when two groups have the same last row.
Does anyone have a solution?
Images show the macros script and grouping Image of marcos
Image of grouping
Below is the VBA script that was written for creating the effort via user form.
Private Sub ButtonAddEffort_Click()
Dim c As Object
Dim sht As Worksheet
Dim foundrow As Long
Dim blassign As Boolean
Dim x As Long
Dim rowstart As Long
Dim rowend As Long
Dim i As Long
Dim rowstarteffort As Long
If IsNull(Me.txtProjectNumberLocate) Or Me.txtProjectNumberLocate = "" Then
MsgBox "Please enter a project number."
Me.txtProjectNumberLocate.SetFocus
Exit Sub
End If
If IsNull(Me.txtEffortName) Or Me.txtEffortName = "" Then
MsgBox "Please enter an effort name."
Me.txtEffortName.SetFocus
Exit Sub
End If
If Not IsNull(Me.txtStartDate) And Me.txtStartDate <> "" Then
If Not IsDate(Me.txtStartDate) Then
MsgBox "Please enter a valid start date in 'mm/dd/yyyy' format."
Me.txtStartDate.SetFocus
Exit Sub
End If
End If
If Not IsNull(Me.txtFinishDate) And Me.txtFinishDate <> "" Then
If Not IsDate(Me.txtFinishDate) Then
MsgBox "Please enter a valid finish date in 'mm/dd/yyyy' format."
Me.txtFinishDate.SetFocus
Exit Sub
End If
End If
Set sht = Sheets("Sheet1")
Set c = sht.Range("F:F").Find(what:=Me.txtProjectNumberLocate, after:=sht.Cells(1, 6), LookIn:=xlValues, lookat:=xlWhole, searchorder:=xlByRows, searchdirection:=xlPrevious, MatchCase:=False)
If Not c Is Nothing Then
foundrow = c.Row
rowstart = foundrow
rowstarteffort = foundrow
Else
foundrow = 0
End If
If foundrow = 0 Then
MsgBox "Could not find project # " & Me.txtProjectNumberLocate
Exit Sub
End If
''any efforts exist1
Set c = sht.Range("A:A").Find(what:="*", after:=sht.Cells(foundrow, 1), LookIn:=xlValues, lookat:=xlPart, searchorder:=xlByRows, searchdirection:=xlNext, MatchCase:=False)
If Not c Is Nothing Then
foundrownext = c.Row
Else
foundrownext = 0
End If
If foundrownext > foundrow Then
foundrow = foundrownext - 1
End If
'check work order format
For x = 1 To 8
If Not IsNull(Me("txtworkorder" & x)) And Me("Txtworkorder" & x) <> "" Then
If Me("CheckBox" & x) = True Then
If Len(Me("txtWorkOrder" & x)) <> 8 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Me("txtWorkOrder" & x), "-") = 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If Mid(Me("txtworkorder" & x), 5, 1) <> "-" Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Left(Me("txtWorkOrder" & x), 4), "-") <> 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
If InStr(1, Right(Me("txtWorkOrder" & x), 3), "-") <> 0 Then
MsgBox "Work order numbers must be in 'xxxx-xxx' format."
Me("txtWorkOrder" & x).SetFocus
Exit Sub
End If
End If
End If
Next x
i = 0
If foundrownext > 1 Then
sht.Rows(rowstart + 1 & ":" & foundrownext - 1).Select
On Error Resume Next
Selection.Rows.Ungroup
On Error GoTo 0
End If
blassign = False
For x = 8 To 1 Step -1
If Me("CheckBox" & x) = True Then
blassign = True
End If
Next x
If blassign = False Then
sht.Range(foundrow + 1 & ":" & foundrow + 1).EntireRow.Insert shift:=xlDown
sht.Range("B" & foundrow + 1) = Me.txtEffortName
sht.Range("B" & foundrow + 1).Font.Color = 13998939
sht.Range("B" & foundrow + 1).Font.Underline = True
sht.Range("I" & foundrow + 1) = Me.txtStartDate
sht.Range("J" & foundrow + 1) = Me.txtFinishDate
i = 1
Else
sht.Range(foundrow + 1 & ":" & foundrow + 1).EntireRow.Insert shift:=xlDown
sht.Range("B" & foundrow + 1) = Me.txtEffortName
sht.Range("B" & foundrow + 1).Font.Color = 13998939
sht.Range("B" & foundrow + 1).Font.Underline = True
sht.Range("I" & foundrow + 1) = Me.txtStartDate
sht.Range("J" & foundrow + 1) = Me.txtFinishDate
For x = 8 To 1 Step -1
If Me("CheckBox" & x) = True Then
sht.Range(foundrow + 2 & ":" & foundrow + 2).EntireRow.Insert shift:=xlDown
sht.Range("F" & foundrow + 2) = Me("txtWorkOrder" & x)
sht.Range("G" & foundrow + 2) = Me("cmbAssign" & x)
i = i + 1
End If
Next x
End If
''group new efforts
If foundrownext <= 1 Then
foundrownext = rowstart + 1
End If
sht.Rows(foundrow + 2 & ":" & foundrownext + i).Select
Selection.Rows.Group
''ungroup and group old project data
rowend = foundrownext + i - 1
sht.Rows(rowstart + 1 & ":" & rowend).Select
Selection.Rows.Group
''
MsgBox "Done!"
End Sub
Private Sub ButtonClose_Click()
Unload Me
End Sub
Private Sub ComboBox1_Change()
End Sub
Private Sub ComboBox2_Change()
End Sub
Private Sub ComboBox3_Change()
End Sub
Private Sub ComboBox4_Change()
End Sub
Private Sub TextBox9_Change()
End Sub
Private Sub UserForm_Click()
End Sub

Outline (group) in Excel requires a summary row, that depending on the settings you have in your computer, should be placed below (default) or above each outline level.
Your situation
What's happening in your spreadsheet is that you currently have the default settings, i.e. summary row should be below the current outline level. And you're grouping the rows 9,10 and 13.
My guess here is that the developer tried to group effort 1 and effort 2 and it didn't work, because to group effort 2 without leaving an additional row would just look like this:
Note: See the 4 dots on the right of rows 13 to 16
The Excel solution
In this case, you need to toggle the settings so the summary rows are above the detail
How to adjust the settings
Outline settings:
Current configuration:
Adjusted configuration
This would allow to have the summary row above details like this:
And when collapsed:
The VBA solution
Now, about the VBA code you have, although it can certainly be improved, I understand it accomplishes your requirements.
I suggest to specially check these two blocks:
Block # 1:
''group new efforts
If foundrownext <= 1 Then
foundrownext = rowstart + 1
End If
sht.Rows(foundrow + 2 & ":" & foundrownext + i).Select
Selection.Rows.Group
Block #2
''ungroup and group old project data
rowend = foundrownext + i - 1
sht.Rows(rowstart + 1 & ":" & rowend).Select
Selection.Rows.Group
I'd suggest the developer to read this article on how and why to avoid select in Excel VBA.
Please let me know if the solution works and remember to mark the answer (tick the check mark at the left) if it does.

Related

How to apply same code to new worksheets?

I want to duplicate the code below so it applies to every new sheet.
I have to manually change the code to the new sheet's name. I found loops but that coding didn't work for me. I am trying to create a stop watch function for billing hours.
I can copy sheets and create a copy, but then none of the functions work in the new sheet it says
Run-Time Error 1004: select method of Range class failed
and stops on this line:
Sheets("Client").Range("B" & iRow).Select
Sub Intialize()
Dim iRow As Long
iRow = Sheets("Client").Range("F" & Application.Rows.Count).End(xlUp).Row
'Code to Validate
If Sheets("Client").Range("D" & iRow).Value = "" Then
Sheets("Client").Range("A" & iRow).Value = Format([Today()], "DD-MMM-YYYY")
End If
End Sub
Sub Start_Time()
Dim iRow As Long
iRow = Sheets("Client").Range("F" & Application.Rows.Count).End(xlUp).Row + 1
'Code to Validate
If Sheets("Client").Range("B" & iRow).Value = "" Then
MsgBox "Please select the Task Name from the drop down.", vbOKOnly + vbInformation, "Task Name Blank"
Sheets("Client").Range("B" & iRow).Select
Exit Sub
ElseIf Sheets("Client").Range("D" & iRow).Value <> "" Then
MsgBox "Start Time is aleady captured for the selected Task."
Exit Sub
Else
Sheets("Client").Range("D" & iRow).Value = [Now()]
Sheets("Client").Range("D" & iRow).NumberFormat = "hh:mm:ss AM/PM"
End If
End Sub
Sub End_Time()
Dim iRow As Long
iRow = Sheets("Client").Range("F" & Application.Rows.Count).End(xlUp).Row + 1
'Code to Validate
If Sheets("Client").Range("D" & iRow).Value = "" Then
MsgBox "Start Time has not been captured for this task."
Exit Sub
Else
Sheets("Client").Range("E" & iRow).Value = [Now()]
Sheets("Client").Range("E" & iRow).NumberFormat = "hh:mm:ss AM/PM"
Sheets("Client").Range("F" & iRow).Value = Sheets("Client").Range("E" & iRow).Value - Sheets("Client").Range("D" & iRow).Value
Sheets("Client").Range("F" & iRow).NumberFormat = "hh:mm:ss"
End If
Call Intialize
End Sub
Well, since you said:
1 Need to copy a single Sheet
2 Apply a code to that sheet, whatever the name
3 You will create (copy) several sheet.
Here is my code.
(Paste everything in a normal module)
Option Explicit
Const A = 1
Const B = 2
Const D = 4
Const E = 5
Const F = 6
Const L = 1048576 'Excel.Application.Rows.Count
'With this you can check if you can copy the sheet
'and also, return that sheet you already checked,
'no matter the name of that sheet.
Private Function SetSheet(sht As Worksheet) As Worksheet
'This function validate if the sheet is one that you need to copy
'Assuming the first sheets of the book are used to:
'
'Parameters 1
'Main 2
'Other... 3
If sht.Index >= 4 Then 'Here is where (Sheet #4 and so on) begins...
Set SetSheet = sht
Else
'Message or do nothing...
End
End If
End Function
Sub Intialize()
' You can uncomment this DIM vars but
' need to comment the const above.
' Dim F: F = Range("F1").Column
' Dim D: D = Range("D1").Column
' Dim A: A = Range("A1").Column
' Dim L: L = Application.Rows.Count
Dim ActSht As Worksheet: Set ActSht = SetSheet(ActiveSheet)
Dim iRow As Long
iRow = ActSht.Range(Cells(L, F), Cells(L, F)).End(xlUp).Row
' Code to Validate
If ActSht.Range(Cells(iRow, D), Cells(iRow, D)).Value = "" Then
ActSht.Range(Cells(iRow, A), Cells(iRow, A)).Value = Format([Today()], "DD-MMM-YYYY")
End If
End Sub
Sub Start_Time()
' Dim B: B = Range("B1").Column
' Dim F: F = Range("F1").Column
' Dim D: D = Range("D1").Column
' Dim L: L = Application.Rows.Count
Dim ActSht As Worksheet: Set ActSht = SetSheet(ActiveSheet)
Dim iRow As Long
iRow = ActSht.Range(Cells(L, F), Cells(L, F)).End(xlUp).Row + 1
' Code to Validate
If ActSht.Range(Cells(iRow, B), Cells(iRow, B)).Value = "" Then
MsgBox "Please select the Task Name from the drop down.", vbOKOnly + vbInformation, "Task Name Blank"
ActSht.Range(Cells(iRow, B), Cells(iRow, B)).Select
Exit Sub
ElseIf ActSht.Range(Cells(iRow, D), Cells(iRow, D)).Value <> "" Then
MsgBox "Start Time is aleady captured for the selected Task."
Exit Sub
Else
ActSht.Range(Cells(iRow, D), Cells(iRow, D)).Value = [Now()]
ActSht.Range(Cells(iRow, D), Cells(iRow, D)).NumberFormat = "hh:mm:ss AM/PM"
End If
End Sub
Sub End_Time()
' Dim D: D = Range("D1").Column
' Dim F: F = Range("F1").Column
' Dim E: E = Range("E1").Column
' Dim L: L = Application.Rows.Count
Dim ActSht As Worksheet: Set ActSht = SetSheet(ActiveSheet)
Dim iRow As Long
iRow = ActSht.Range(Cells(L, F), Cells(L, F)).End(xlUp).Row + 1
' Code to Validate
If ActSht.Range(Cells(iRow, D), Cells(iRow, D)).Value = "" Then
MsgBox "Start Time has not been captured for this task."
Exit Sub
Else
ActSht.Range(Cells(iRow, E), Cells(iRow, E)).Value = [Now()]
ActSht.Range(Cells(iRow, E), Cells(iRow, E)).NumberFormat = "hh:mm:ss AM/PM"
ActSht.Range(Cells(iRow, F), Cells(iRow, F)).Value = Sheets("Client").Range("E" & iRow).Value - Sheets("Client").Range("D" & iRow).Value
ActSht.Range(Cells(iRow, F), Cells(iRow, F)).NumberFormat = "hh:mm:ss"
End If
Call Intialize
End Sub
Asummtions:
1 You call one or several of this sub-rutines from the original worksheet.
Note:
It is better not to use hardcoded this way Sheets("Client").Range("D" & iRow).Value because when you need to debug... It hurts! That is why I do prefer ActSht.Range(Cells(iRow, D), Cells(iRow, D)).Value and you can control a single var above all the code.

Comment Used To Track Changes

I have encountered a few issues with some code in VBA. I am trying to have the changes made to a cells on an excel sheet show up in comments on the cell the change was made to and I wish for these changes to be stored in a list so I can view them all later. I have tried lots of different pieces of code I have found to try and implement it into the code but none have worked.
Any ideas on how to get this to work?
Worksheet
The below code is what I am currently using
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Adding As Boolean, Finding As Boolean, Subtracting As Boolean
Dim f As Range, v
Select Case Target.Address(True, True)
Case "$A$4": Adding = True
Case "$C$4": Subtracting = True
Case "$E$4": Finding = True
Case Else: Exit Sub
End Select
v = Trim(Target.Value)
If Len(v) = 0 Then Exit Sub
Set f = Me.Range("C8").Resize(1000, 1).Find(v, lookat:=xlWhole)
If Adding Then
If f Is Nothing Then
'not found: add as new row
Set f = Me.Cells(Rows.Count, 3).End(xlUp).Offset(1, 0)
f.Value = v
End If
f.Offset(0, 1).Value = f.Offset(0, 1).Value + 1
doDate f.Offset(0, 2)
Target.Value = ""
ElseIf Subtracting Then
If f Is Nothing Then
MsgBox v & " not found for subtraction!"
Else
f.Offset(0, 1).Value = f.Offset(0, 1).Value - 1
doDate f.Offset(0, 3)
Target.Value = ""
End If
Else 'finding
If Not f Is Nothing Then
f.EntireRow.Select
Target.Value = ""
Else
MsgBox v & " not found."
End If
End If
If Adding Or Subtracting Then Target.Select
End Sub
Sub doDate(c As Range)
With c
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End Sub
I have implemented a few formulas on the worksheet but don't see any reason why it would matter in this situation since they only track quantity of items with the same unique identifier.
I also tried some code that added comments to the cells as they were changed that worked but always returned the previous cell value as blank. It is not actually added into the current code though.
Option Explicit
Public preValue As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
Target.ClearComments
Target.AddComment.Text Text:="Previous Value was " & preValue & Chr(10) & "Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & "By " & Environ("UserName")
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
If Target = "" Then
preValue = "a blank"
Else: preValue = Target.Value
End If
End Sub
By and large, the code below should do what you want. I marveled at your use of A4 and C4 to express addition and subtraction. As it is, whatever you change in those two cells, apart from clearing them, will result in a quantity of 1 being added or subtracted. I would have expected that a quantity must be entered there which is processed. If the quantity is fixed at 1 the system appears too elaborate.
Anyway, here's the code. I guess you'll be able to modify it to better suit your needs.
Private Sub Worksheet_Change(ByVal Target As Range)
' 038
Dim LookUp As Variant ' subject
Dim Action As Variant ' add = 1, subtract = -1, find = 2
Dim Fnd As Range ' Result of Find method
Dim Txt As String ' comment text
With Target
If (.Row <> 4) Or (.CountLarge > 1) Then Exit Sub
LookUp = Cells(4, "E").Value
On Error Resume Next
Action = Array(0, 1, 0, -1, 0, 2)(.Column)
End With
If Action And (LookUp <> "") Then
' C8 to end of column C
With Range(Cells(8, "C"), Cells(Rows.Count, "C").End(xlUp))
Set Fnd = .Find(LookUp, .Cells(.Cells.Count), xlValues, xlWhole, xlByRows)
End With
End If
If Fnd Is Nothing Then
Select Case Action
Case -1
MsgBox """" & LookUp & """ not found.", vbInformation, "Can't subtract"
Action = -2
Case 2
MsgBox """" & LookUp & """ not found.", vbInformation, "No record"
Action = -2
Case Else
Set Fnd = Cells(Rows.Count, "C").End(xlUp).Offset(1)
Fnd.Value = LookUp
End Select
End If
With Fnd
If Abs(Action) <> 2 Then
With .Offset(0, 1)
If .Comment Is Nothing Then
.AddComment
Else
Txt = Chr(10)
End If
Txt = "Previous Qty = " & .Value & Chr(10) & _
"Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & _
"by " & Environ("UserName") & Txt
.Comment.Text Txt, 1, False
.Value = Val(.Value) + Action
With .Offset(0, 2)
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End With
ElseIf Action = 2 Then
.EntireRow.Select
End If
End With
If Action <> 2 Then Target.Select
End Sub

VBA Userform Listbox Conditional Logic Not Working as Intended

I have a Userform with a listbox for which I am using conditional logic to determine output values to the sheet of selected or non-selected items in the listbox. The issue is that when the Textbox (Tbl_AliasName) is blank, the code executes this:
ElseIf .Selected(k) = True And Tbl_AliasName = vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Trim(Cells(2, 1).Value2) & "." & .Column(1, k)
But if Tbl_AliasName is not blank then the code does nothing, but it is supposed to do this:
ElseIf .Selected(k) = True And Tbl_AliasName <> vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Tbl_AliasName & "." & .Column(1, k)
I have used several variations of If statements, and non of which have worked.
Below is My Code:
Option Explicit
Public Tbl_AliasName As String
Tbl_AliasName = Trim(UserForm_Finder.txtConcat.Value)
Private Sub BtnConcat_Click()
Dim k As Long, lstbxRow As Long, LR As Long
lstbxRow = 1
'****************
'This if statement works perfectly
If (Cells(2, 1).Value2 = vbNullString Or Cells(2, 2).Value2 = vbNullString) _
And Tbl_AliasName = vbNullString Then
MsgBox "You must Search for a Table or Column first.", _
vbExclamation, "Error Encountered"
Exit Sub
ElseIf (UserForm_Finder.ListBx_TblsCols.ListCount = 0 And Tbl_AliasName <> vbNullString) Then
MsgBox "You must Search for a Table or Column first.", _
vbExclamation, "Error Encountered"
'(Cells(2, 1).Value2 = vbNullString Or Cells(2, 2).Value2 = vbNullString) And _
Exit Sub
End If
With UserForm_Finder.ListBx_TblsCols
For k = 0 To .ListCount - 1
'****************
This is where the problems begin
If .Selected(k) = False Then
MsgBox "You must Select 1 or more items from the list box.", _
vbExclamation, "Error Encountered"
Exit Sub
ElseIf .Selected(k) = True And Tbl_AliasName <> vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Tbl_AliasName & "." & .Column(1, k)
ElseIf .Selected(k) = True And Tbl_AliasName = vbNullString Then
lstbxRow = lstbxRow + 1
Worksheets("New TRAX").Cells(lstbxRow, 3) = Trim(Cells(2, 1).Value2) & "." & .Column(1, k)
End If
Next k
End With
End Sub
My goal is to do the following:
If a Textbox (Tbl_AliasName) is not blank and the user has selected one or more items in the listbox (ListBx_TbleCols) then concatenate the Tbl_AliasName to the selected items in the listbox
If Tbl_AliasName is blank, then use the value in Cells(2,1) to concatenate to the selected Items in the list box.
I have tried the following additions:
Dim LstBxItemSelected As Boolean
'This was placed in the for loop
LstBxItemSelected = True
'this was placed outside the for loop
If LstBxItemSelected = False Then
MsgBox "You must Select 1 or more items from the list box.", _
vbExclamation, "Error Encountered"
Exit Sub
End If
Is there a better way to tell if items are selected, because I feel that the way I have it structured in my loop, the code will throw the error if everything isn't selected? Thank you in advance for any ideas, answers, or suggestions!
Note: The Listbox is populated by the click of another button on the userform which calls the following sub:
Sub FillLstBxCols()
Dim ListBx_Target As MSForms.ListBox
Dim rngSource As Range
Dim LR As Long
If Cells(2, 1).Value2 <> vbNullString Then
LR = Worksheets("New TRAX").Cells(Rows.Count, 2).End(xlUp).Row
'Set reference to the range of data to be filled
Set rngSource = Worksheets("New Trax").Range("A" & 2 & ":" & "B" & LR)
'Fill the listbox
Set ListBx_Target = UserForm_Finder.ListBx_TblsCols
With ListBx_Target
.RowSource = rngSource.Address
End With
End If
End Sub
Hard to say without sample data and expected results, but I think this is what you're looking for:
Private Sub btnConcat_Click()
Dim ws As Worksheet
Dim bSelected As Boolean
Dim sConcat As String
Dim i As Long, lRowIndex As Long
Set ws = ActiveWorkbook.Sheets("New TRAX")
lRowIndex = 1
bSelected = False
sConcat = Trim(Me.txtConcat.Text)
If Len(sConcat) = 0 Then sConcat = Trim(ws.Cells(2, "A").Value)
If Len(sConcat) = 0 Then
MsgBox "You must Search for a Table or Column first.", vbExclamation, "Error Encountered"
Exit Sub
End If
For i = 0 To Me.ListBx_TblsCols.ListCount - 1
If Me.ListBx_TblsCols.Selected(i) Then
If bSelected = False Then
bSelected = True
ws.Range("C2", ws.Cells(ws.Rows.Count, "C")).Clear 'clear previous concat results (delete this line if not needed)
End If
lRowIndex = lRowIndex + 1
ws.Cells(lRowIndex, "C").Value = sConcat & "." & Me.ListBx_TblsCols.List(i)
End If
Next i
If bSelected = False Then MsgBox "Must select at least one item from the list"
End Sub

Assign value to a cell using VBA

I am trying to put the value of the cell that is after the one with the string "Senast", into cell Q2. The code below does not work. Any ideas why?
Sub Find_Data()
Dim item_in_review As Variant
Dim row_number As Long
For row_number = 1 To 1000 Step 1
item_in_review = Sheets("Investor_Importerad data").Range("A" & row_number)
If InStr(item_in_review, "Senast") Then
row_number = row_number + 1
Worksheets("Översikt innehavCells").Cells(2, "Q").Value = Worksheets("Investor_Importerad data").Cells(row_number, "A").Value
Exit For
End If
If item_in_review = 300 Then
MsgBox "300"
Exit For
End If
Next row_number
End Sub
It seems that you are looking to either a wildcard match to Senast or the number 300 as a true number. Whichever comes first, you exit your For ... Next loop.
The following is filled with numerous error trapping and criteria conditions that should isolate your problem.
Sub Find_Data()
Dim rSenast As Variant, r300 As Variant, wsn As String
wsn = "Översikt innehavCells"
Dim row_number As Long
With Worksheets("Investor_Importerad data")
rSenast = Application.Match("*senast*", .Range("a:a"), 0)
r300 = Application.Match(300, .Range("a:a"), 0)
If IsError(r300) Then _
r300 = Application.Match(Format(300, "0"), .Range("a:a"), 0)
Select Case True
Case CBool(IsNumeric(rSenast) And IsNumeric(r300))
If r300 < rSenast Then
If MsgBox("300 found before 'senast" & vbLf & "Continue with value transfer?", vbYesNo, "Continue?") <> vbYes Then _
Exit Sub
End If
Case CBool(IsError(rSenast) And IsError(r300))
MsgBox "Neither 'senast' nor 300 can be found"
Exit Sub
Case CBool(IsError(rSenast) And IsNumeric(r300))
MsgBox "300 can be found at row " & r300
Exit Sub
End Select
On Error GoTo No_Such_Worksheet
If .Cells(rSenast + 1, "A").Value <> vbNullString Then
Worksheets(wsn).Cells(2, "Q") = .Cells(rSenast + 1, "A").Value
Else
MsgBox .Cells(rSenast + 1, "A").Address(0, 0) & " appears to be empty"
End If
On Error GoTo 0
End With
Exit Sub
No_Such_Worksheet:
With Worksheets.Add(after:=Worksheets("Investor_Importerad data"))
.Name = wsn
End With
Resume
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