stop save changes if multiple conditions not met excel vba - vba

I'm new in vba, i've been spending time and time surfing on internet trying to find solution but i could'nt
can someone assist on the below code, i want excel to force users to inter data in column K L and S whenever column B filled in with data (i.e column B is not empty) before save it.
may i know what i've missed below to make it run?!
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Set rng = Worksheets("Sheet1").Range("B7:B10000")
Application.ScreenUpdating = False
For Each cell In rng
If Not IsEmpty(cell) And IsEmpty(cell.Offset(-1, 9)) Then
Application.Goto cell.Offset(-1, 9)
Cancel = True
MsgBox "Save is cancelled!" & _
vbNewLine & "" & vbNewLine & "Please fill in cells in column K."
End If
Next
Application.ScreenUpdating = True
End Sub
Private Sub column_L()
Set rng = Worksheets("Sheet1").Range("B7:B10000")
Application.ScreenUpdating = False
For Each cell In rng
If Not IsEmpty(cell) And IsEmpty(cell.Offset(-1, 10)) Then
Application.Goto cell.Offset(-1, 10)
Cancel = True
MsgBox "Save is cancelled!" & vbNewLine & "" & vbNewLine & "Please fill in cells in column L."
End If
Next
Application.run macro:="column_L"
End Sub
Private Sub column_S()
Set rng = Worksheets("Sheet1").Range("B7:B10000")
Application.ScreenUpdating = False
For Each cell In rng
If Not IsEmpty(cell) And IsEmpty(cell.Offset(-1, 17)) Then
Application.Goto cell.Offset(-1, 17)
Cancel = True
MsgBox "Save is cancelled!" & _
vbNewLine & "" & vbNewLine & "Please fill in cells in column S."
End If
Next
Application.run macro:="column_S"
End Sub

You had two subroutines (column_L and column_S) that weren't being called from anywhere but, if they had been, they would have gone into an infinite loop because they called themselves.
Your code should just be placed in the BeforeSave event as follows:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim rng As Range
Set rng = Worksheets("Sheet1").Range("B7:B10000")
Cancel = True
For Each cell In rng
If Not IsEmpty(cell) Then
'Check column K of current row
If IsEmpty(cell.Offset(0, 9)) Then
Application.Goto cell.Offset(0, 9)
MsgBox "Save is cancelled!" & _
vbNewLine & vbNewLine & "Please fill in cells in column K."
Exit Sub
End If
'Check column L of current row
If IsEmpty(cell.Offset(0, 10)) Then
Application.Goto cell.Offset(0, 10)
MsgBox "Save is cancelled!" & _
vbNewLine & vbNewLine & "Please fill in cells in column L."
Exit Sub
End If
'Check column S of current row
If IsEmpty(cell.Offset(0, 17)) Then
Application.Goto cell.Offset(0, 17)
MsgBox "Save is cancelled!" & _
vbNewLine & vbNewLine & "Please fill in cells in column S."
Exit Sub
End If
End If
Next
Cancel = False
End Sub
(I removed your ScreenUpdating code, as your code doesn't actually update any cells, so I don't think you should see any flickering of the screen other than the movement caused by the GoTo, and you probably want the users to see that.)

Related

Excel VBA Looking for Text in a Whole Column

I have the following code to find the phrase "Please Review" in Column "I" and if not found show message, if found it must run the rest of my code but its not liking my IF code:
Sub OUTPUT()
Sheets("OUTPUT").Select
If Range("a2").Value < 1 Then
Else
Range("A2:I" & Range("A1").End(xlDown).Row + 1).ClearContents
End If
Sheets("SF Data").Select
If Range("I2:I192754").Value <> "Please Review" Then
MsgBox "Nudda"
Else
Columns("A:I").Select
Selection.AutoFilter
Range("I2").Select
ActiveSheet.Range("A1:I" & Range("A1").End(xlDown).Row + 1).AutoFilter Field:=9, Criteria1:= _
"Please Review"
Range("A2:I" & Range("A1").End(xlDown).Row + 1).Select
Selection.SpecialCells(xlCellTypeVisible).Select
Application.CutCopyMode = False
Selection.Copy
Sheets("OUTPUT").Select
Range("A2").Select
ActiveSheet.Paste
Sheets("SF Data").Select
ActiveSheet.Range("$A$1:$I$192754").AutoFilter Field:=9
Sheets("OUTPUT").Select
End If
MsgBox "Sanity Check performed. " & Format(Now, "mmmm d, yyyy hh:mm AM/PM")
End Sub
Many thanks guys
I believe the following should help you out, you should try to avoid Select & Activate statements, also by declaring your worksheets your code is more legible, to find the string I used the Find method and allocated the result to a Range variable to see if anything was found:
Sub OUTPUT()
Dim wsOut As Worksheet: Set wsOut = Sheets("OUTPUT")
Dim wsSF As Worksheet: Set wsSF = Sheets("SF Data")
'Declare and set the worksheets you are working with
Dim FoundPlease As Range
Dim LastRow As Long
If wsOut.Range("A2").Value > 1 Then wsOut.Range("A2:I" & wsOut.Range("A1").End(xlDown).Row + 1).ClearContents
'Clear contents if A2 > 1
LastRow = wsSF.Cells(wsSF.Rows.Count, "I").End(xlUp).Row
'find the last row with data on Column I in SF Data
Set FoundPlease = wsSF.Range("I2:I" & LastRow).Find(What:="Please Review", LookAt:=xlWhole)
'Search for "Please Review" on Column I in SF Data
If FoundPlease Is Nothing Then 'if not found
MsgBox "Nudda"
Else 'if found
wsSF.Cells.AutoFilter
wsSF.Range("A1:I" & wsSF.Range("A1").End(xlDown).Row + 1).AutoFilter Field:=9, Criteria1:="Please Review"
wsSF.Range("A2:I" & wsSF.Range("A1").End(xlDown).Row + 1).SpecialCells(xlCellTypeVisible).Copy
wsOut.Range("A2").PasteSpecial xlPasteAll
wsSF.Range("$A$1:$I$" & LastRow).AutoFilter Field:=9
End If
MsgBox "Sanity Check performed. " & Format(Now, "mmmm d, yyyy hh:mm AM/PM")
End Sub
It is giving an error, because Range("I2:I192754").Value <> "Please Review" is a bit illegal. If you want to check whether in one of the cells in the range there is the string "Please Review" present, you may consider using =CountIf() function:
Sub TestMe()
If WorksheetFunction.CountIf(Range("I2:I192754"), "Please Review") > 0 Then
Debug.Print WorksheetFunction.CountIf(Range("I2:I192754"), "Please Review")
End If
End Sub
Later, you may take a look at this topic - How to avoid using Select in Excel VBA.
One way would be to check each cell individually:
Dim TextFound As Boolean
TextFound=False
For Each cell In Range("I2:I192754")
If cell.value="Please Review" Then
TextFound=True
Exit For
End If
Next
If TextFound Then
...
Else
...
End IF

VBA Type Mismatch (Run time error 13) when pasting a whole row of data

I'm currently facing issues and I've no idea why..
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns("B:B")) Is Nothing Then Exit Sub
If Target.Value = "Yes" Then
Range(Range("A" & Target.Row), Range("I" & Target.Row)).Copy _
Sheets("UpdateModify Forms").Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Range(Range("AC" & Target.Row), Range("AU" & Target.Row)).Copy _
Sheets("UpdateModify Forms").Range("J" & Rows.Count).End(xlUp).Offset(1, 0)
ElseIf Target.Value = "No" Then
Range(Range("A" & Target.Row), Range("AB" & Target.Row)).Copy _
Sheets("Development Forms").Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Range(Range("AW" & Target.Row), Range("AY" & Target.Row)).Copy _
Sheets("Development Forms").Range("AC" & Rows.Count).End(xlUp).Offset(1, 0)
End If
End Sub
The Error is pointing on
If Target.Value = "Yes" Then
How it should work:
when i paste a row filled with data, by right it should check for the criteria wheter it's Yes or No, and sorting it to their distinctive sheet.
BUT
It appeared the run time error 13 for some reason.. i only know how to bypass it which is to manually type in data and avoid the column where it will trigger the validation first and key in other datas first and finally that validation column.
One more question:
Is there anyway to print the whole thing without me inputting the validation column LAST? Because i tried to key the validation column first and when i continue filling up the row beside it, it dont show on the next sheet, only displaying "Yes" or "No" Which is the Column B
Any help would be appreciated, thanks for the time!
If you update a whole row, Target will be a whole row. You can't test whether an entire row is "Yes" - you need to look at a single cell.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns("B")) Is Nothing Then Exit Sub
Dim cel As Range
For Each cel In Intersect(Target, Columns("B:B")).Cells
If cel.Value = "Yes" Then
Range(Range("A" & cel.Row), Range("I" & cel.Row)).Copy _
Sheets("UpdateModify Forms").Range("A" & Sheets("UpdateModify Forms").Rows.Count).End(xlUp).Offset(1, 0)
Range(Range("AC" & cel.Row), Range("AU" & cel.Row)).Copy _
Sheets("UpdateModify Forms").Range("J" & Sheets("UpdateModify Forms").Rows.Count).End(xlUp).Offset(1, 0)
ElseIf cel.Value = "No" Then
Range(Range("A" & cel.Row), Range("AB" & cel.Row)).Copy _
Sheets("Development Forms").Range("A" & Sheets("Development Forms").Rows.Count).End(xlUp).Offset(1, 0)
Range(Range("AW" & cel.Row), Range("AY" & cel.Row)).Copy _
Sheets("Development Forms").Range("AC" & Sheets("Development Forms").Rows.Count).End(xlUp).Offset(1, 0)
End If
Next
End Sub
As for the other question as to whether there is a way to have this process only after the whole row has been filled in, I would suggest you have a button and have the user click the button when they finish - thus avoiding a Worksheet_Change event. Or at least make the very last column be the one to trigger the Change.
In response to comment requesting values:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns("B:B")) Is Nothing Then Exit Sub
Dim cel As Range
For Each cel In Intersect(Target, Columns("B:B")).Cells
If cel.Value = "Yes" Then
With Sheets("UpdateModify Forms")
With .Range("A" & .Rows.Count).End(xlUp).Offset(1, 0).EntireRow
.Range("A1:I1").Value = Rows(cel.Row).Range("A1:I1").Value
'I commented out the next two lines because I assume the
'last row in column A should be the same as the last row
'in column J - uncomment them if that is not the case.
'End With
'With .Range("J" & .Rows.Count).End(xlUp).Offset(1, 0).EntireRow
.Range("J1:AB1").Value = Rows(cel.Row).Range("AC1:AU1").Value
End With
End With
ElseIf cel.Value = "No" Then
With Sheets("Development Forms")
With .Range("A" & .Rows.Count).End(xlUp).Offset(1, 0).EntireRow
.Range("A1:AB1").Value = Rows(cel.Row).Range("A1:AB1").Value
'End With
'With .Range("AC" & .Rows.Count).End(xlUp).Offset(1, 0).EntireRow
.Range("AC1:AE1").Value = Rows(cel.Row).Range("AW1:AY1").Value
End With
End With
End If
Next
End Sub
when multiple cells is selected, it would be error when you trying to use target.value. Try use target.values when selection.cells.count > 1.
First I suggest passing the intersection into a variable,
Dim MyTarget as range
Set MyTarget = Intersect(Target, Columns("B:B"))
Next check if there is an intersection by,
If MyTarget Is Nothing Then Exit Sub
You can now loop through that variable using
Dim EachCell as Range
For Each EachCell in MyTarget
'do your thing here
Next
-Romcel

Tracking Share-drive Users name and opening time in Excel?

I have found a similar article about my question, as stated below;
How do I track who uses my Excel spreadsheet?
However, I do like the last coloumn of comment >>
"You could also put a time stamp in the next column to show when the spreadsheet was used"
My question is> can anyone guide me the possible step or let me copy the code for doing this please? and how to hide the worksheet without anyone noticing?
My key is, very importantly, everything must done silently which no one else (other users in sharedrive) could find out i m tracking it. The reason is , i have done lot of research worksheets, and i don't have time/impossible to make every single excel worksheet perfect, i need to prioritize them inorder to be efficient with my time by knowing which one is more important to people.
many thanks~!!
In Excel, under the Review tab, you have 'Track Changes'. This should do everything you want.
If you want a VBA script to do this, try one of the following code samples.
Private Sub Worksheet_Change(ByVal Target As Range)
Set t = Target
Set a = Range("A:A")
If Intersect(t, a) Is Nothing Then Exit Sub
Application.EnableEvents = False
t.Offset(0, 7).Value = Environ("username")
Application.EnableEvents = True
End Sub
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
Dim V As Long
Application.EnableEvents = False
Set rng1 = Application.Union(Range("a1:g1"), Range("H:iv"))
Set rng = Application.Intersect(Target, rng1)
If Not rng Is Nothing Then Exit Sub
V = Target.Offset(0, 12).Value
If Target.Offset(0, 12) = "" Then
With Range("H" & Target.Row)
.Value = Target.Address & ": first entry by " & Application.UserName & " at " & Now()
.ColumnWidth = 60
.Interior.ColorIndex = 33
End With
Target.Offset(0, 12).Value = Target.Value
Application.EnableEvents = True
Exit Sub
End If
Target.Offset(0, 12).Value = Target.Value
With Range("H" & Target.Row)
.Value = Target.Address & " changed from " & V & " to " & Target.Value & " by " & Application.UserName & " at " & Now()
.ColumnWidth = 60
.Interior.Color = vbYellow
End With
Application.EnableEvents = True
End Sub
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
With Target
If .Count > 1 Then Exit Sub
If Not Intersect(Range("A2:A10"), .Cells) Is Nothing Then
Application.EnableEvents = False
Sheets("Sheet2").Select
If IsEmpty(.Value) Then
.Offset(0, 1).ClearContents
Else
With .Offset(0, 1)
.NumberFormat = "dd mmm yyyy hh:mm:ss"
.Value = Now
End With
End If
Sheets("Sheet1").Select
Application.EnableEvents = True
End If
End With
End Sub
All of these 'Worksheet_Change' scripts are worksheet events. You need to right-click your sheet and click 'View Code' then paste the script into the window that opens. Try one at a time, not all three together.

Need help in locking selected range of cells using VBA

I need help in locking specific range of cells based on the selection of value from list from another cell.
To be specific, I have created data validation list for columns N5 to N36, upon selection of value "Exist" from the cell N5, I want to lock that specific row O5 to U5.
i.e "Exist" in N6 would lock O6 to U6 and so on.
Similarly for the other rows till N36.
And if the user selects "Does not Exist", then I would want those cells to remain unlocked and editable similar to the above condition.
I have tried macros from various forums using my very basic knowledge of using macros, but most of those lock the entire sheet.
Code I tried:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("N5:N36")) Is Nothing Then
ActiveSheet.Unprotect
If Target.Value = "Exist" Then
Range("O" & Target.Column & ":U" & Target.Column).Select Selection.Locked = False
Else
Range("O" & Target.Column & ":U" & Target.Column).Select Selection.Locked = True
End If
End If
ActiveSheet.Protect DrawingObjects:=True, Contents:=True, Scenarios:=True
End Sub
I would really appreciate your quick help.
Thanks in Advance.
Is this what you are trying (Tried And Tested)? Also see THIS. It's worth a read.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rw As Long
Dim sPass As String
'~~> Password
sPass = "BlahBLah"
On Error GoTo Whoa
'~~> For excel 2003 use .Count instead of .CountLarge
'~~> In case of multiple cells were changed
If Target.Cells.CountLarge > 1 Then Exit Sub
Application.EnableEvents = False
If Not Intersect(Target, Range("N5:N36")) Is Nothing Then
If UCase(Trim(Target.Value)) = "EXIST" Then
rw = Target.Row
With ActiveSheet
.Unprotect sPass
.Cells.Locked = False
.Range("O" & rw & ":U" & rw).Locked = True
.Protect Password:= sPass , DrawingObjects:=True, _
Contents:=True, Scenarios:=True
End With
End If
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
you could do something like this:
Sub LockCells()
'unprotect the sheet
ActiveSheet.Unprotect
'unlock all cells
Cells.Locked = False
Cells.FormulaHidden = False
Dim cell As Range
'find all cells that need to be locked
For Each cell In Range("N5:N36")
If cell = "Exist" Then
Range("O" & cell.Row & ":U" & cell.Row).Locked = True
Range("O" & cell.Row & ":U" & cell.Row).FormulaHidden = True
End If
Next cell
'protect the sheet
ActiveSheet.Protect DrawingObjects:=True, Contents:=True, Scenarios:=True
End Sub

Copy excel row in different worksheet when cell dropdown "Yes" and when "No" removes the row if "Yes" was selected previously

I am trying to copy excel row in different worksheet sheet 2 when cell dropdown "Yes" of Column F and when "No" removes the row if "Yes" was selected previously. I also wanted to check if duplicate exists in worksheet 2, then prompt user with "Yes", "No" button. If "Yes" then duplicate if "No" do nothing.
ColA:Customer Name ColB:Customer Address ColC:Customer City ColD:Cust zip ColE:Tel ColF:Yes/No
I have tried this.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim lastrow As Long
If UCase(Range("F" & ActiveCell.Row).Value) <> "YES" Then Exit Sub
With ThisWorkbook.Worksheets("Sheet2")
lastrow = Application.Max(4, .Cells(.Rows.Count, "A").End(xlUp).Row + 1)
If WorksheetFunction.CountIf(.Range("A1:A" & lastrow), _
Range("A" & ActiveCell.Row).Value) > 0 Then Exit Sub
Response = MsgBox("Record already exists, add again?", vbQuestion + vbYesNo + 256)
If Response = vbNo Then Exit Sub
.Range("A" & lastrow).Resize(, 5).Value = _
Range("A" & ActiveCell.Row).Resize(, 5).Value
End With
Response = MsgBox("Record added")
End Sub
If I understand you correctly, you need something like this (code runs only if changed value in column F):
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lastrow As Long
Dim Response
Dim rng As Range, rngToDel As Range
Dim fAddr As String
If Intersect(Target, Range("F:F")) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo ErrHandler
With ThisWorkbook.Worksheets("Sheet2")
lastrow = Application.Max(4, .Cells(.Rows.Count, "A").End(xlUp).Row + 1)
If UCase(Target.Value) = "YES" Then
Response = vbYes
If WorksheetFunction.CountIf(.Range("A1:A" & lastrow), _
Range("A" & Target.Row).Value) > 0 Then
Response = MsgBox("Record already exists, add again?", vbQuestion + vbYesNo + 256)
End If
If Response = vbYes Then
.Range("A" & lastrow).Resize(, 5).Value = _
Range("A" & Target.Row).Resize(, 5).Value
MsgBox "Record added"
End If
ElseIf UCase(Target.Value) = "NO" Then
With .Range("A4:A" & lastrow)
Set rng = .Find(What:=Range("A" & Target.Row), _
LookIn:=xlValues, _
lookAt:=xlWhole, _
MatchCase:=False)
If Not rng Is Nothing Then
fAddr = rng.Address
Do
If rngToDel Is Nothing Then
Set rngToDel = rng.Resize(, 5)
Else
Set rngToDel = Union(rngToDel, rng.Resize(, 5))
End If
Set rng = .FindNext(rng)
If rng Is Nothing Then Exit Do
Loop While fAddr <> rng.Address
End If
If Not rngToDel Is Nothing Then
rngToDel.Delete Shift:=xlUp
MsgBox "Records from sheet2 removed"
End If
End With
End If
End With
ExitHere:
Application.EnableEvents = True
Exit Sub
ErrHandler:
Resume ExitHere
End Sub