Conflict between Modules Excel VBA - vba

The two modules below always run in a loop.
I want the second module for verification that a record was created after the first module runs, since all the user sees is the question, but not the result.
First module detects when new row is added to a table and asks if you want to export data to another worksheet:
Sub NewDatabaseEntry()
Dim sh As Worksheet
Dim rspn As VbMsgBoxResult
rspn = MsgBox("Do you want to create a project? If you did not add a new row, click No", vbYesNo)
If rspn = vbNo Then Exit Sub
Range("MasterTemplate").Copy
Sheets("Database").Range("C" & Rows.Count).End(xlUp).Offset(1).PasteSpecial Paste:=xlPasteFormulas
FindProjectName 'A macro that literally finds the name of the project...
'FindRow
End Sub
This module then looks at the row number on the destination worksheet and then copies that row number value to a predefined range.
Sub FindRow()
Application.ScreenUpdating = False
Dim LastRow As Long
LastRow = Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.ScreenUpdating = True
End Sub
The only way FindRow works is when I place it in the ThisWorkbook~ Excel Object.
If I place it anywhere else, it gets into a loop with the NewDatabaseEntry module where it keeps asking you if youwant to create a new project.`
I would've liked for the user to know that the entry was created without having to close out of the workbook and then reopening it, just to verify what row number their record was placed on.
Is there something I am missing?

Since you mention that this gets stuck in an infinite loop to ask if they want to create a new project, I believe that the reason is because you have a Worksheet_Change event (or similar) that fires off when you add a value to the Projects worksheet.
The problem comes in when you have your FindProject manipulating data on the same worksheet that your Worksheet_Change event is looking for.
So what I believe you should do is turn off events until FindProject is done (by the way, I would recommend changing FindProject to something else because it does more than just "find a project").
Sub FindRow()
Application.ScreenUpdating = False
Application.EnableEvents = False ' ADDED THIS
Dim LastRow As Long
LastRow = Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.EnableEvents = True ' ADDED THIS
Application.ScreenUpdating = True
End Sub

Related

Create a AllowEditRange conditional to a value on a column range

I have the code below which allow me to unprotect a sheet with an AllowEditRange, verify which rows of a range in column C has data on it and write the work "Ok" on column B in the rows where data was found in column C. The code also protects the sheet in the end returning to normal with my AllowEditRange but I need that the rows where the "Ok" was stamped are taken out of the AllowEditRange, blocking them for further edition. In other words I'm looking for a way to cancel these rows from the AllowEditRange or delete the range and create a new one excluding the rows with "Ok" in column B.
I'm trying to incorporate something like:
Dim aer As AllowEditRange
For Each aer In ActiveSheet.Protection.AllowEditRanges
aer.Delete
If InStr(-1, cell.Value, "") <> 0 Then
Set aer = workbook.Protection.AllowEditRanges.Add("Edition", workbook.Range("A1:D4"))
aer.Users.Add "Power Users", True
End If
But it's not working no matter what I do. Any help?
Sub Test()
ActiveSheet.Unprotect Password:="Maze"
Dim mainworkBook As Workbook
Set mainworkBook = ActiveWorkbook
Application.ScreenUpdating = False
Dim lastRow As Long
Dim cell As Range
lastRow = Range("C" & Rows.Count).End(xlUp).Row
For Each cell In Range("C32:C70" & lastRow)
If InStr(1, cell.Value, "") <> 0 Then
cell.Offset(, -1).Value = "Ok"
End If
Next
Application.ScreenUpdating = True
ActiveSheet.Protect Password:="Maze"
End Sub
As it was giving me a huge headache and consuming loads of time, I gave up of the AllowEditRanges and came up with a a work around. I just split the code in two and used the good old lock and unlock cells. I'm leaving the code below if anybody got decides to go for it too. Also, the code I came up with is very slow and after a couple of hours I decided to ask if anybody has a faster alternative.
Sub LockRow()
Dim rChk As Range, r1st As Range
Set r1st = Columns("B").Find(What:="Ok", _
after:=Cells(Rows.Count, "B"), _
LookIn:=xlValues, lookat:=xlPart, _
searchdirection:=xlNext)
If Not r1st Is Nothing Then
Set rChk = r1st
Do
ActiveSheet.Unprotect Password:="Maze"
rChk.EntireRow.Locked = True
ActiveSheet.Protect Password:="Maze"
Set rChk = Columns("B").FindNext(after:=rChk)
Loop While rChk.Address <> r1st.Address
End If
Set r1st = Nothing
Set rChk = Nothing
End Sub

VBA - Find all matches across multiple sheets

I am working on a macro that will search an entire workbook for various codes. These codes are all six digit numbers. Codes I wish to search for are input in column A of a sheet called "Master". If a code found on another sheet matches one in Master it's sheet name and cell will be pasted in column B next to it's match in Master. When successful the end result looks like this.
The code posted below works in certain cases, but fails quite often. Occasionally a run-time error will appear, or an error message with "400" and nothing else. When these errors occur the macro fills a row with matches for a blank value at the end of all the listed codes. This is obviously not an intended function.
I am at a loss regarding the above error. I have wondered if limiting the search range would help stability. All codes on other sheets are only found in column A, so searching for matches in all columns as is done currently is quite wasteful. Speed is secondary to stability however, I first want to eliminate all points of failure.
Sub MasterFill()
Dim rngCell As Range
Dim rngCellLoc As Range
Dim ws As Worksheet
Dim lngLstRow As Long
Dim lngLstCol As Long
Dim strSearch As String
Sheets("Master").Select
lngLstRowLoc = Sheets("Master").UsedRange.Rows.Count
Application.ScreenUpdating = False
For Each rngCellLoc In Range("A1:A" & lngLstRowLoc)
i = 1
For Each ws In Worksheets
If ws.Name = "Master" Then GoTo SkipMe
lngLstRow = ws.UsedRange.Rows.Count
lngLstCol = ws.UsedRange.Columns.Count
ws.Select
For Each rngCell In Range(Cells(2, 1), Cells(lngLstRow, lngLstCol))
If InStr(rngCell.Value, rngCellLoc) > 0 Then
If rngCellLoc.Offset(0, i).Value = "" Then
rngCellLoc.Offset(0, i).Value = ws.Name & " " & rngCell.Address
i = i + 1
End If
End If
Next
SkipMe:
Next ws
Next
Application.ScreenUpdating = True
Worksheets("Master").Activate
MsgBox "All done!"
End Sub
See if this doesn't expedite matters while correcting the logic.
Sub MasterFill()
Dim addr As String, fndCell As Range
Dim rngCellLoc As Range
Dim ws As Worksheet
Application.ScreenUpdating = False
With Worksheets("Master")
For Each rngCellLoc In .Range(.Cells(1, "A"), .Cells(.Rows.Count, "A").End(xlUp))
For Each ws In Worksheets
If LCase(ws.Name) <> "master" Then
With ws.Columns("A")
Set fndCell = .Find(what:=rngCellLoc.Value2, After:=.Cells(1), _
LookIn:=xlFormulas, LookAt:=xlPart, _
MatchCase:=False, SearchFormat:=False)
If Not fndCell Is Nothing Then
addr = fndCell.Address(0, 0)
Do
With rngCellLoc
.Cells(1, .Parent.Columns.Count).End(xlToLeft).Offset(0, 1) = _
Join(Array(ws.Name, fndCell.Address(0, 0)), Chr(32))
End With
Set fndCell = .FindNext(After:=fndCell)
Loop While addr <> fndCell.Address(0, 0)
End If
End With
End If
Next ws
Next
.Activate
End With
Application.ScreenUpdating = True
MsgBox "All done!"
End Sub
I've used LookAt:=xlPart in keeping with your use of InStr for criteria logic; if you are only interested in whole cell values change this to LookAt:=xlWhole.
I've restricted the search range to column A in each worksheet.
Previous results are not cleared before adding new results.
Your own error was due to the behavior where a zero length string (blank or vbNullString) is found within any other string when determined by Instr.

Strikethrough associated cells when value in column changes

I have an excel sheet that has three columns: employee number employee name availability What I am trying to do is when the availability value changes from a number to nothing the employee number and the employee name associated with that row gets a strikethrough. Also when an availability number is added the strikethrough disappears. I have written some code below but I have no idea if I am going in the right direction.
Sub change(ByVal Target As Range)
Dim ws As Worksheet
Dim watchrange As Range
dim intersectrange as range
Set ws = Worksheets("Workbench Report")
endrow = ws.Cells(ws.Rows.count, "E").End(xlUp).Row
Set watchrange = Range("E2:E" & endrow)
Set intersectrange = Intersect(Target, watchrange)
If intersectrange = "" Then
ws.Range("B" & rng.Row).Resize(1, 2).Font.Strikethrough = True
Else
'do nothing
End If
End Sub
Could someone help me?
Thank you in advance
With data like:
This worksheet event macro:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim watchrange As Range, r As Range, rw As Long
Dim intersectrange As Range, endrow As Long
endrow = Cells(Rows.Count, "A").End(xlUp).Row
Set watchrange = Range("C2:C" & endrow)
Set intersectrange = Intersect(Target, watchrange)
If intersectrange Is Nothing Then Exit Sub
For Each r In intersectrange
rw = r.Row
If r.Value = "" Then
Range("A" & rw & ":B" & rw).Font.Strikethrough = True
Else
Range("A" & rw & ":B" & rw).Font.Strikethrough = False
End If
Next r
End Sub
will meet your needs. You need to adjust the columns to match your data schema.
Because it is worksheet code, it is very easy to install and automatic to use:
right-click the tab name near the bottom of the Excel window
select View Code - this brings up a VBE window
paste the stuff in and close the VBE window
If you have any concerns, first try it on a trial worksheet.
If you save the workbook, the macro will be saved with it.
If you are using a version of Excel later then 2003, you must save
the file as .xlsm rather than .xlsx
To remove the macro:
bring up the VBE windows as above
clear the code out
close the VBE window
To learn more about Event Macros (worksheet code), see:
http://www.mvps.org/dmcritchie/excel/event.htm
EDIT#1:
This code is triggered by changes to column C and reside in the worksheet code area for that sheet.
If your button code changes those column C values, then this event code would work with it.

Copy information from one workbook to another with a specific condition

Hi I would need a code to allow me to copy paste the information from a workbook called "Target" to another workbook called "Source" based on a specific condition.
This condition is based on the unique Project ID found in the code.
I tried doing some coding but it does not seems to allow me to get the result that I wanted.
The code would only read the first row and copy the information to the other workbook instead of looking through the project ID "10000327" in the "Project ID" column in the Target workbook and copy the information to the Source workbook.
Below is the code that I have tried and gave the result that I mentioned earlier.
Really hope that anyone could help me as I am very new to VBA. Thank you:)
Sub AAA()
Dim source As Worksheet
Dim target As Worksheet
Dim cellFound As Range
Set target = Workbooks("Target.xlsm").Sheets("Sheet1")
Set source = Workbooks("Source.xlsm").Sheets("Sheet2")
lastrow = source.Range("A" & target.Rows.Count).End(xlUp).Row
lastcol = target.Cells(2, target.Columns.Count).Column
target.Activate
'For a = 2 To 50
For Each cell In target.Range("A2:A500")
' Try to find this value in the source sheet
Set cellFound = source.Range("A:A").Find(What:="10000327", LookIn:=xlValues, LookAt:=xlWhole)
If Not cellFound Is Nothing Then
cell.Offset(ColumnOffset:=1).Copy
cellFound.Offset(ColumnOffset:=1).PasteSpecial xlPasteValues
Else
Exit Sub
End If
Next
I've changed the hard-coded search term to a var that gets it's pid on successive loops.
Sub AAB()
Dim sWS As Worksheet, tWS As Worksheet
Dim pidCol As Long, pidRow As Long, pidStr As String, rw as long
Set tWS = Workbooks("Target.xlsm").Sheets("Sheet1")
Set sWS = Workbooks("Source.xlsm").Sheets("Sheet2")
With sWS
With .Cells(1, 1).CurrentRegion
pidCol = 1
pidStr = "10000327" '.Cells(rw, pidCol).Value
If CBool(Application.CountIf(.Columns(1), pidStr)) Then
rw = Application.Match(pidStr, .Columns(1), 0)
With .Cells(rw, 2).Resize(1, .Columns.Count - 1)
If CBool(Application.CountIf(tWS.Columns(1), pidStr)) Then
pidRow = Application.Match(pidStr, tWS.Columns(1), 0)
.Copy Destination:=tWS.Cells(pidRow, 2)
End If
End With
End If
End With
End With
Set sWS = Nothing
Set tWS = Nothing
End Sub
This loops through all the values in column A (pidCol = 1) on the source worksheet and copies the data to the target worksheet if the associated PID is found on the target worksheet.
If I understand your question correctly, I think what's going on here is that your for loop is running the find command once for each cell, but it runs the same find command, only returning the first match, each time. If you are using the find command, I think you can use a do...while loop more appropriately, then use "findnext." The msdn help gives an example of this which I think is exactly what you want to do:
With Worksheets(1).Range("a1:a500")
Set c = .Find(2, lookin:=xlValues)
If Not c Is Nothing Then
firstAddress = c.Address
Do
c.Value = 5
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
The other option would be to check if each cell you get to in your for loop matches.

Effective Looping Checkup VBA

Summary: My company has two different spreadsheets with many policies on each. They want me to match up policies by a policy ID and transfer all the old notes from the old spreadsheet to the new spreadsheet.
Reasoning: my issue is not with not understanding how to do this, but the BEST way to do this. Since joining StackOverflow I've been told things I should and shouldn't do. I've been told different times it is better to use a For Each loop instead of a simple Do loop. Also, I've been told I shouldn't use .Select heavily (but I do).
How I Would Normally Do It: I would normally just use a Do Loop and go through the data just selecting the data with .Find and using ActiveCell and when I wanted to interact with other Columns in that current row I would just use ActiveCell.Offset(). I tend to love .Select and use it all the time, however on this project I'm trying to push myself out of the box and maybe change some bad coding habits and start using what may be better.
Question: How would I go about doing the equivalent of an ActiveCell.Offset() when I'm using a For Each loop?
My Code So Far: **Questions/Criticisms welcome
Sub NoteTransfer()
transferNotes
End Sub
Function transferNotes()
Dim theColumn As Range
Dim fromSheet As Worksheet
Dim toSheet As Worksheet
Dim cell As Range
Dim lastRow As Integer
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet 'FINDING LAST ROW
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
End With
Set theColumn = fromSheet.Range("B5:B" & lastRow)
For Each cell In theColumn 'CODE FOR EACH CELL IN COLUMN
If cell.Text = "" Then
'do nothing
Else
With toSheet 'WANT TO FIND DATA ON THE toSheet
Cells.find(What:=cell.Text, After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
End With
End If
Next cell
End Function
Example
Bottom of the sheet
First, your question:
Question: How would I go about doing the equivalent of an ActiveCell.Offset() when I'm using a For Each loop?
Doesn't make much sense given the code you posted. It's a very general question, and would need some context to better understand. It really depends on your loop. If you are looping a contiguous range of cells from the ActiveCell then you could say ...
For each cel in Range
myValue = ActiveCell.Offset(,i)
i = i + 1
Next
To get the column next to each cell in the loop. But in general I wouldn't call that great programming. Like I said, context is important.
As far as your code goes, see if this makes sense. I've edited and commented to help you a bit. Oh yeah, good job not using Select!
Sub transferNotes() '-> first no need for a function, because you are not returning anything...
'and no need to use a sub to call a sub here as you don't pass variables,
'and you don't have a process you are trying to run
Dim theColumn As Range, cell As Range '-> just a little cleaner, INMHO
Dim fromSheet As Worksheet, toSheet As Worksheet '-> just a little cleaner, INMHO
Dim lastRow As Integer
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet ' -> put everything you do in the "fromSheet" in your With block
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row 'FINDING LAST ROW
Set theColumn = .Range("B5:B" & lastRow)
theColumn.AutoFilter 1, "<>"
Set theColumn = theColumn.SpecialCells(xlCellTypeVisible) '-> now you are only looping through the cells are that are not blank, so it's more efficient
For Each cell In theColumn
'-> use of ActiveCell.Offset(), it's not ActiveCell.Offset(), but it uses Offset
Dim myValue
myValue = cell.Offset(, 1) '-> gets the cell value in the column to the right of the code
'WANT TO FIND DATA ON THE toSheet
toSheet.Cells.Find(What:=cell.Text, After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
Next cell
End With
End Sub
This is my suggestion so far.
Function transferNotes()
Dim SourceColumn As Range
Dim fromSheet As Worksheet
Dim toSheet As Worksheet
Dim cell As Range
Dim lastRow As Long '<--changed to Long
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet 'FINDING LAST ROW
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
End With
Set SourceColumn = fromSheet.Range("B5:B" & lastRow)
For Each cell In SourceColumn 'CODE FOR EACH CELL IN COLUMN
If cell.Value = "" Then 'the .Text property can
'make for some confusing errors.
'Try to avoid it.
'nothng to search for
Else
With toSheet 'WANT TO FIND DATA ON THE toSheet
Dim destRng As Range
Set destRng = .Range("A:A").Find(What:=cell.Value)
If Not destRng Is Nothing Then
.Cells(destRng.Row, <your mapped column destination>)
= fromSheet.Cells(cell.Row,<your mapped column source>)
' you can either repeat the above line for all of your non-contiguous
'sections of data you want to move from sheet to sheet
'(i.e. if the two sheets are not arranged the same)
'if the two sheets are aranged the same then change
'the .cells call to call a range and include
'the full width of columns
Else
'nothing was found
End If
End With
End If
Next cell
End Function