Insert/Delete Columns via VBA issue - excel

I have a workbook that was previously working with no issues. Recently, however, I have been having a problem with adding/deleting columns on visible sheets after I run certain macros.
The workbook is used for groups of members. When data is imported into the file a base number of members are included. Throughout the use of the file the group can expand or contract. I have macros that will add new members or delete existing ones. These macros simply add data or remove it from specific data sheets. Another macro is used to refresh the keycells range that is used to adjust the columns on the visible sheets.
The issue I am having is that once I either add a new member or delete one, the code to increase or delete columns on the visible sheets does not work. The macro that refreshes the sheet doesn't work, nor does manually adjusting the cell itself.
If I do not import any data and simply add or delete columns from visible sheets (based on changing the keycells value), the code runs perfectly. It seems to only occur when I import data and try using macros that add or delete members. For example, without any data, I can add in 3 members and have new columns added in to each visible sheet. I can then reduce that number manually to 1 or 2 and have the appropriate number of columns deleted for each sheet. This works fine until data is imported and the other mentioned macros are used.
I also am experiencing an issue with the file where once I receive an error, even if I reset the VBA, I cannot continue working in it. I can maneuver throughout the file, however, adding or deleting columns (by any means) does not work. It's as though, even though the VBA was reset in the editor, the code does not exist.
This is the code that is used to refresh the keycell
Sub Refresh_ActivesheetB30()
Dim dwsNames As Variant: dwsNames = Array("DATA Member-19", "DATA Sch A-19", "DATA Sch A-3-19", "DATA Sch J-19", "DATA Sch R-19", "DATA 500U-19", "DATA 500U-P-19", "DATA 500U-PA-19")
frmWait.Show vbModeless
DoEvents
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim wb As Workbook: Set wb = ThisWorkbook
Dim gws As Worksheet: Set gws = wb.Worksheets("GroupInfo")
gws.Range("B30").Formula = "=COUNTIF('TAX INFO'!B34:B1499,"">0"")"
Dim dws As Worksheet
Dim dlRow As Long
Dim d As Long
For d = LBound(dwsNames) To UBound(dwsNames)
On Error Resume Next
Set dws = wb.Worksheets(dwsNames(d))
On Error GoTo 0
If Not dws Is Nothing Then
dlRow = dws.Range("D" & dws.Rows.Count).End(xlUp).Row
dws.Range("A12").Copy dws.Range("A12:A" & dlRow)
Set dws = Nothing
End If
Next d
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
gws.Activate
frmWait.Hide
End Sub
This code adds members
Option Explicit
Private Sub CommandButton1_Click()
Dim ws As Worksheet
Dim N As Long
Dim i As Long
Dim Rng1 As Range
Dim Rng2 As Range
Dim Rng3 As Range
Dim Rng4 As Range
Dim Rng5 As Range
Set ws = ActiveSheet
Set Rng1 = ws.Range("6:6").Find(Me.TextBox2.Value)
Set Rng2 = ws.Range("6:6").Find(Me.TextBox6.Value)
Set Rng3 = ws.Range("6:6").Find(Me.TextBox5.Value)
Set Rng4 = ws.Range("6:6").Find(Me.TextBox4.Value)
Set Rng5 = ws.Range("6:6").Find(Me.TextBox7.Value)
N = ActiveSheet.Cells(Rows.Count, "B").End(xlUp).Row
If WorksheetFunction.CountIfs(ws.Range("6:6"), TextBox2, ws.Range("6:6"), TextBox2) = 0 And ComboBox1 <> 0 Then
MsgBox "Sorry, " & TextBox2 & " not found!"
Else
If TextBox3.Value = "" And ComboBox1.Value <> "" Then
MsgBox "There is no data to add", 48
Else
If WorksheetFunction.CountIfs(ws.Range("6:6"), TextBox6, ws.Range("6:6"), TextBox6) = 0 And ComboBox2 <> 0 Then
MsgBox "Sorry, " & TextBox6 & " not found!"
Else
If TextBox8.Value = "" And ComboBox2.Value <> "" Then
MsgBox "There is no data to add", 48
Else
If WorksheetFunction.CountIfs(ws.Range("6:6"), TextBox5, ws.Range("6:6"), TextBox5) = 0 And ComboBox3 <> 0 Then
MsgBox "Sorry, " & TextBox5 & " not found!"
Else
If TextBox9.Value = "" And ComboBox3.Value <> "" Then
MsgBox "There is no data to add", 48
Else
If WorksheetFunction.CountIfs(ws.Range("6:6"), TextBox4, ws.Range("6:6"), TextBox4) = 0 And ComboBox4 <> 0 Then
MsgBox "Sorry, " & TextBox4 & " not found!"
Else
If TextBox10.Value = "" And ComboBox4.Value <> "" Then
MsgBox "There is no data to add", 48
Else
If WorksheetFunction.CountIfs(ws.Range("6:6"), TextBox7, ws.Range("6:6"), TextBox7) = 0 And ComboBox5 <> 0 Then
MsgBox "Sorry, " & TextBox7 & " not found!"
Else
If TextBox11.Value = "" And ComboBox5.Value <> "" Then
MsgBox "There is no data to add", 48
Else
For i = 5 To N
If ActiveSheet.Cells(i, "B").Value = frmAddAdj.ComboBox1.Value Then
ActiveSheet.Cells(i, Rng1.Column).Value = frmAddAdj.TextBox3.Text
End If
If ActiveSheet.Cells(i, "B").Value = frmAddAdj.ComboBox2.Value Then
ActiveSheet.Cells(i, Rng2.Column).Value = frmAddAdj.TextBox8.Text
End If
If ActiveSheet.Cells(i, "B").Value = frmAddAdj.ComboBox3.Value Then
ActiveSheet.Cells(i, Rng3.Column).Value = frmAddAdj.TextBox9.Text
End If
If ActiveSheet.Cells(i, "B").Value = frmAddAdj.ComboBox4.Value Then
ActiveSheet.Cells(i, Rng4.Column).Value = frmAddAdj.TextBox10.Text
End If
If ActiveSheet.Cells(i, "B").Value = frmAddAdj.ComboBox5.Value Then
ActiveSheet.Cells(i, Rng5.Column).Value = frmAddAdj.TextBox11.Text
End If
Next i
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End Sub
Private Sub CommandButton2_Click()
Unload frmAddAdj
End Sub
Private Sub CommandButton3_Click()
Dim ctl As MSForms.Control
For Each ctl In Me.Controls
Select Case TypeName(ctl)
Case "TextBox", "ComboBox"
ctl.Text = ""
Case "CheckBox", "OptionButton", "ToggleButton"
ctl.Value = False
End Select
Next ctl
End Sub
Private Sub UserForm_Initialize()
Dim iRow As Integer, iMax As Integer
iRow = Cells.Find(What:="New Jersey Audit Adjustment", _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Row
iMax = Cells.Find(What:="New Jersey Audit Adjustment", _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious, _
MatchCase:=False, SearchFormat:=False).Row
If ActiveSheet.Range("B" & iRow & ":B" & iMax).Cells.Count = 1 Then
Me.ComboBox1.AddItem ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox2.AddItem ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox3.AddItem ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox4.AddItem ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox5.AddItem ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Else
Me.ComboBox1.List = ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox2.List = ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox3.List = ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox4.List = ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
Me.ComboBox5.List = ActiveSheet.Range("B" & iRow & ":B" & iMax).Value
End If
End Sub
This is the code to delete members
Private Sub CommandButton1_Click()
'declare the variables
Dim Findvalue As Range, DeleteRange As Range
Dim Response As VbMsgBoxResult
Dim cNum As Integer
Dim Search As String, FirstAddress As String
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("DATA Member-19")
'error statement
On Error Resume Next
Search = TextBox6.Value
'check for control from listbox dblclick values
If TextBox6.Value = "" Or Search = "" Then
MsgBox "There is not data to delete", 48
Exit Sub
Else
'find the employees number row
Set Findvalue = ws.Range("D:D").Find(What:=Search, LookIn:=xlValues, LookAt:=xlWhole)
If Not Findvalue Is Nothing Then
'mark first address
FirstAddress = Findvalue.Address
'give the user a chance to change their mind!
Response = MsgBox(Search & Chr(10) & _
"Are you sure that you want to delete this Member?", 292, "Are you sure?")
If Response = vbYes Then
'find all matching records
Do
If DeleteRange Is Nothing Then
Set DeleteRange = Findvalue
Else
Set DeleteRange = Union(DeleteRange, Findvalue)
End If
Set Findvalue = ws.Range("D:D").FindNext(Findvalue)
Loop While FirstAddress <> Findvalue.Address
'delete record(s)
DeleteRange.EntireRow.Delete
'clear the user form controls
cNum = 12
For x = 1 To cNum
Me.Controls("Reg" & x).Value = ""
Next
'Employee deleted from the database
MsgBox Search & Chr(10) & "The Member has been deleted successfully.", 64, "Record Deleted"
'add the values to the listbox
lstLookup.RowSource = ""
End If
Else
MsgBox Search & Chr(10) & "Record Not Found", 48, "Not Found"
End If
End If
End Sub
Private Sub CommandButton2_Click()
Unload frmDeleteMembers19
End Sub
This is the code that goes into the main sheet module
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range, colNum As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
SOMESHEETS = "*C-Proposal-19*MemberInfo-19*Schedule J-19*NOL-19*NOL-P-19*NOL-PA-19*Schedule R-19*Schedule A-3-19*Schedule A-19*Schedule H-19*"
Set KeyCells = Range("B30")
If Not Application.Intersect(KeyCells, Target) Is Nothing Then
If IsNumeric(KeyCells.Value) Then
colNum = KeyCells.Value
If colNum > 0 Then
For Each ws In ThisWorkbook.Worksheets
If ws.Visible = xlSheetVisible Then
If CBool(InStr(LCase(SOMESHEETS), LCase("*" & ws.Name & "*"))) Then
InsertColumnsOnSheet argSheet:=ws, argColNum:=colNum
End If
End If
Next ws
End If
End If
End If
SOMESHEETS = "*MemberInfo-20*C-Proposal-20*Schedule J-20*NOL-20*Schedule R-20*NOL-P-20*SchA-3-20*Schedule H-20*NOL-PA-20*Schedule A-20*Schedule A-5-20*"
Set KeyCells = Range("B36")
If Not Application.Intersect(KeyCells, Target) Is Nothing Then
If IsNumeric(KeyCells.Value) Then
colNum = KeyCells.Value
If colNum > 0 Then
For Each ws In ThisWorkbook.Worksheets
If ws.Visible = xlSheetVisible Then
If CBool(InStr(LCase(SOMESHEETS), LCase("*" & ws.Name & "*"))) Then
InsertColumnsOnSheet argSheet:=ws, argColNum:=colNum
End If
End If
Next ws
End If
End If
End If
Application.ScreenUpdating = True
End Sub
And this is the general code that each sheet pulls from. I only included on sheet to save some space, but each sheet has similar code.
Public Sub InsertColumnsOnSheet(ByVal argSheet As Worksheet, ByVal argColNum As Long)
Dim Rng As Range, c As Range
Dim TotalCol As Long, LeftFixedCol As Long
Dim i As Long
Dim ws As Worksheet
Dim j As Integer, k As Integer
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Set ws = Worksheets("C-Proposal-19")
With argSheet
Set Rng = .Range(.Cells(3, 6), .Cells(3, .Columns.Count))
Set c = Rng.Find("GROSS")
If Not c Is Nothing Then
TotalCol = c.Column
LeftFixedCol = 5
j = .Range("B4").End(xlToRight).Column
k = j - LeftFixedCol
If ws.Visible = xlSheetVisible Then
If TotalCol < LeftFixedCol + argColNum + 1 Then
.Columns(j).Copy
.Columns(j + 1).Resize(, argColNum - k).Insert CopyOrigin:=xlFormatFromLeftOrAbove
Application.CutCopyMode = False
End If
End If
If TotalCol > LeftFixedCol + argColNum + 1 Then
For i = TotalCol - 1 To LeftFixedCol + argColNum + 1 Step -1
.Columns(i).Delete
Next i
End If
End If
End With
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
End Sub
Moreover, if I add a member and refresh the keycells range, a new column will be added. If I then try to reduce the amount of columns manually, it will reduce the amount of columns. The issue pops up when I try use the delete macro. After I delete out the member through that macro, I cannot add or delete columns, either manually or through the refresh macro.
There also seems to be an issue where if I have an error pop up with VBA, the workbook does not use the code that I have in it. For example, if I change the general code to add/delete columns and I get an error, even if I reset the VBA in the editor, if I try and change the keycells nothing happens. I don't get the same error again, even though I don't change the code at all, and nothing happens to any of the sheets.

There is a bug in the first part of your code:
For d = LBound(dwsNames) To UBound(dwsNames)
On Error Resume Next
Set dws = wb.Worksheets(dwsNames(d))
On Error GoTo 0
If Not dws Is Nothing Then
dlRow = dws.Range("D" & dws.Rows.Count).End(xlUp).Row
dws.Range("A12").Copy dws.Range("A12:A" & dlRow)
Set dws = Nothing
End If
Next d
This shows the problem:
Dim ws As Worksheet, e
For Each e In Array("Sheet1", "Sheet2")
On Error Resume Next
Set ws = ThisWorkbook.Sheets(e)
On Error GoTo 0
If Not ws Is Nothing Then Debug.Print e, ws.Name
Next e
Run in a workbook which contains only Sheet1, it gives this output:
Sheet1 Sheet1
Sheet2 Sheet1 'oops!
So you need to add Set dws = Nothing before running Set dws = wb.Worksheets(dwsNames(d))

Related

Two questions about VBA in excel: 1. Return a column name and return old value of a cell

I'm creating a spreadsheet with logdetails of another sheet with information that is changing constantly and I have to keep tracking the changes. I was able to record into the logdetails spreadsheet part of the changes
but not the column name (based on the cell address and the old value).
Here it is my VBA code so far.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If ActiveSheet.Name <> "logdetails" Then
Application.EnableEvents = False
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = ActiveSheet.Name & "-" & Target.Address(0, 0)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 3).Value = Target.Value
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 4).Value = Environ("username")
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 5).Value = Now
Sheets("logdetails").Columns("A:H").AutoFit
Application.EnableEvents = True
End If
End Sub
First you need to save the old value somewhere through workbook event. The variable lastRng bellow will save the value of every active cell and it will be restored in case of change
Dim lastRng
Private Sub Workbook_Open()
Set lastRng = ActiveCell
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
lastRng = Target.Value
End Sub
After, you add the next two lines
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = Target.Address
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 2).Value = lastRng
to complete your table as you desire. I didn't understand very well what you means by column name, but if you want the letter instead column number or cell address, you can find good solutions here in this question to convert one in another
All in all, your consolidated code will be like this:
Dim lastRng
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If ActiveSheet.Name <> "logdetails" Then
Application.EnableEvents = False
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = ActiveSheet.Name & "-" & Target.Address(0, 0)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = Target.Address
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 2).Value = lastRng
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 3).Value = Target.Value
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 4).Value = Environ("username")
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 5).Value = Now
Sheets("logdetails").Columns("A:H").AutoFit
Application.EnableEvents = True
End If
End Sub
Private Sub Workbook_Open()
Set lastRng = ActiveCell
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
lastRng = Target.Value
End Sub
A Workbook Sheet Change: Log Changes in Multiple Worksheets
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const LogName As String = "logdetails"
Const DST_COLUMNS_COUNT As Long = 6
On Error GoTo ClearError
If Sh.Name = "logdetails" Then Exit Sub
If Not TypeOf Sh Is Worksheet Then Exit Sub ' not a worksheet
Dim twsName As String: twsName = Sh.Name
Dim usName As String: usName = Environ("USERNAME")
Dim cTime As String: cTime = Now
Dim nDict As Object: Set nDict = DictRangeAddressAndFormulas(Target)
Application.EnableEvents = False
Dim oDict As Object
Application.Undo
Set oDict = DictRangeAddressAndFormulas(Target)
Application.Undo
Dim drCount As Long, nKey
For Each nKey In nDict.Keys
drCount = drCount + UBound(nDict(nKey), 1)
Next nKey
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To DST_COLUMNS_COUNT)
Dim sr As Long, sc As Long, dr As Long, nString As String, oString As String
For Each nKey In nDict.Keys
Debug.Print nKey, nDict(nKey)(1, 1), oDict(nKey)(1, 1)
For sr = 1 To UBound(nDict(nKey), 1)
For sc = 1 To UBound(nDict(nKey), 2)
nString = CStr(nDict(nKey)(sr, sc))
oString = CStr(oDict(nKey)(sr, sc))
If StrComp(nString, oString, vbBinaryCompare) <> 0 Then
dr = dr + 1
With Sh.Range(nKey).Cells(sr, sc)
dData(dr, 1) = twsName & "-" & .Address(0, 0)
dData(dr, 2) = Split(.Address, "$")(1)
End With
dData(dr, 3) = oDict(nKey)(sr, sc)
dData(dr, 4) = nDict(nKey)(sr, sc)
dData(dr, 5) = usName
dData(dr, 6) = cTime
End If
Next sc
Next sr
Next nKey
Dim dws As Worksheet: Set dws = Me.Sheets(LogName)
Dim dlCell As Range: Set dlCell = dws.Cells(dws.Rows.Count, "A").End(xlUp)
Dim drg As Range: Set drg = dlCell.Offset(1).Resize(dr, DST_COLUMNS_COUNT)
drg.Value = dData
drg.EntireColumn.AutoFit
ProcExit:
On Error Resume Next
If Not Application.EnableEvents Then Application.EnableEvents = True
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "':" & vbLf & Err.Description
Resume ProcExit
End Sub
Function DictRangeAddressAndFormulas( _
ByVal rg As Range) _
As Object
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim arg As Range
For Each arg In rg.Areas
dict(arg.Address) = GetRangeFormulas(arg)
Next arg
Set DictRangeAddressAndFormulas = dict
End Function
Function GetRangeFormulas( _
ByVal rg As Range) _
As Variant
Dim Data() As Variant
If rg.Rows.Count * rg.Columns.Count = 1 Then ' one cell
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Formula
Else ' multiple cells
Data = rg.Formula
End If
GetRangeFormulas = Data
End Function

how to add the above row x times in excel based on the number y which can change

I am trying to make an excel file for my parents so that they have it easier than writing all the info in a book X amount of times.
i have A; B; C; D; E; F; G; H; I; J; and L columns and want to automate and duplicate the data in A to G in rows below xn-1 times when
there is a number on Hx cell x amount of times,
where x can be from 1 to 50.
https://preview.redd.it/8p19v7ncjyo91.png?width=1859&format=png&auto=webp&s=5265abb1f6c77b418c409197e19ab836f62bd5ec
before typing 10
https://preview.redd.it/xq9p3m69kyo91.png?width=1384&format=png&auto=webp&s=b06512811b45d8d7c33ff8072d58bc1f8603fa46
example data after inputting 10 or 5 respectively
thus will be inputting all the details in rows 17 and 27
Please, test the next code. It iterates backwards, inserts the necessary number of rows (from "H" cell) and copy on them the values of between columns "A:G" of the row where "H" cell is not empty and numeric:
Sub CopyRowsNTimes()
Dim sh As Worksheet, lastRH As Long, i As Long
Set sh = ActiveSheet 'use here the sheet you need
lastRH = sh.Range("H" & sh.rows.count).End(xlUp).row 'last row on column "H:H")
Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
For i = lastRH To 2 Step -1
If IsNumeric(sh.Range("H" & i).Value) And sh.Range("H" & i).Value <> "" Then
Application.CutCopyMode = False
sh.rows(i + 1 & ":" & i + sh.Range("H" & i).Value - 1).Insert Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
sh.Range("A" & i + 1, "G" & i + 1 + sh.Range("H" & i).Value - 2).Value = _
sh.Range("A" & i, "G" & i).Value
End If
Next i
Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
MsgBox "Ready..."
End Sub
Please, send some feedback after testing it.
I think, clearing the content of H:H column after processing will be a good idea. For the case you run the code for the second time, by mistake. I let it as it was, only to easily check the inserted rows...
Duplicate Rows
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
DuplicateRows Target, "H2", 1, 50
End Sub
Standard Module e.g. Module1
Option Explicit
Sub DuplicateRows( _
ByVal TargetCell As Range, _
ByVal CriteriaColumnFirstCellAddress As String, _
Optional ByVal MinTargetValue As Long = 1, _
Optional ByVal MaxTargetValue As Long = 1)
Const ProcName As String = "DuplicateRows"
On Error GoTo ClearError
' Validate 'TargetCell'.
'If TargetCell Is Nothing Then Exit Sub
If TargetCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Validate 'CriteriaColumnFirstCellAddress'.
Dim ws As Worksheet: Set ws = TargetCell.Worksheet
Dim fCell As Range
On Error Resume Next
Set fCell = ws.Range(CriteriaColumnFirstCellAddress)
On Error GoTo ClearError
If fCell Is Nothing Then Exit Sub ' invalid address
If fCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Build the Criteria (one-column) range ('crg').
Dim rg As Range: Set rg = ws.UsedRange
Dim crg As Range
With fCell
Set crg = Intersect(rg, .Resize(ws.Rows.Count - .Row + 1))
End With
If crg Is Nothing Then Exit Sub ' not intersecting
If Intersect(TargetCell, crg) Is Nothing Then Exit Sub ' not intersecting
' Validate 'MinTargetValue' and 'MaxTargetValue'.
If MinTargetValue < 1 Then Exit Sub
If MaxTargetValue < 1 Then Exit Sub
Dim MinValue As Long
Dim MaxValue As Long
' Handle if min and max are switched.
If MinTargetValue < MaxTargetValue Then
MinValue = MinTargetValue
MaxValue = MaxTargetValue
Else
MinValue = MaxTargetValue
MaxValue = MinTargetValue
End If
' Validate the Target value.
Dim TargetValue As Variant: TargetValue = TargetCell.Value
If Not VarType(TargetValue) = vbDouble Then Exit Sub ' not a number
If Int(TargetValue) <> TargetValue Then Exit Sub ' not a whole number
Select Case TargetValue
Case MinValue To MaxValue
Case Else: Exit Sub ' exceeds the range of numbers
End Select
Dim rrg As Range: Set rrg = Intersect(rg, TargetCell.EntireRow)
Dim LastRow As Long: LastRow = crg.Cells(crg.Cells.Count).Row
Dim MaxInsertRows As Long: MaxInsertRows = ws.Rows.Count - LastRow
If TargetValue > MaxInsertRows Then Exit Sub ' doesn't fit in the worksheet
' (Insert) Copy the data.
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With rrg
If .Row < LastRow Then
.Offset(1).Resize(TargetValue).Insert _
Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
End If
TargetCell.ClearContents
.Copy Destination:=.Resize(TargetValue + 1)
End With
ProcExit:
On Error Resume Next
With Application
If Not .EnableEvents Then .EnableEvents = True
If Not .ScreenUpdating Then .ScreenUpdating = True
End With
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub

Excel VBA: For each row, generate new sheet and copy row to the new sheet

I'm trying to get my spreadsheet to automatically generate new names and sheets based on a data dump. I currently have the sheet working so that it will generate the name and sheet for each row of data, but I cannot get it to populate the sheet using that row.
There is a specific section of code that I cannot get to work:
For Each Nm In shNAMES
If Not Evaluate("ISREF('" & CStr(Nm.Text) & "'!A1)") Then
wsTEMP.Copy After:=.Sheets(.Sheets.Count)
ActiveSheet.Name = CStr(Nm.Text)
ActiveSheet.Range("A1:I1").Value = wsMASTER.Range("A" & Nm & ":I" & Nm) ' <<< This line here
End If
Next Nm
I know that the issue is using Nm to reference the cell (it's returning "OP01" which is the cell contents), but I'm trying to not add another workaround. I've tried using other functions to do similar after the Nm loop has finished, but can't seem to get those working either. Surely the answer has to be simple and I'm just missing something?
Option Explicit
Sub SheetsFromTemplate()
Application.ScreenUpdating = False
Rows("1:8").EntireRow.Delete
Call CreateLONums
Dim wsMASTER As Worksheet, wsTEMP As Worksheet, wsINDEX As Worksheet, wasVISIBLE As Boolean
Dim shNAMES As Range, Nm As Range
Dim firstrow As Long, lastrow As Long
With ThisWorkbook
Set wsTEMP = .Sheets("Template")
wasVISIBLE = (wsTEMP.Visible = xlSheetVisible)
If Not wasVISIBLE Then wsTEMP.Visible = xlSheetVisible
Set wsMASTER = .Sheets("Creation Page")
Set wsINDEX = .Sheets("Local Options")
With Sheets("Creation Page").Columns("A")
If WorksheetFunction.CountA(.Cells) = 0 Then
MsgBox "Sorry: no data"
Else
With .SpecialCells(xlCellTypeConstants)
firstrow = .Areas(1).Row
lastrow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
End If
End With
Set shNAMES = wsMASTER.Range("A" & firstrow, "A" & lastrow)
For Each Nm In shNAMES
If Not Evaluate("ISREF('" & CStr(Nm.Text) & "'!A1)") Then
wsTEMP.Copy After:=.Sheets(.Sheets.Count)
ActiveSheet.Name = CStr(Nm.Text)
ActiveSheet.Range("A1:I1").Value = wsMASTER.Range("A" & Nm & ":I" & Nm)
End If
Next Nm
wsINDEX.Activate
If Not wasVISIBLE Then wsTEMP.Visible = xlSheetHidden
End With
Worksheets("Creation Page").Delete
Worksheets("Template").Delete
Call CreateLinksToAllSheets
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
Sub CreateLONums()
Dim firstrow As Long, lastrow As Long, rowcount As Integer
Columns("A:A").Insert Shift:=xlToRight
With Sheets("Creation Page").Columns("B")
If WorksheetFunction.CountA(.Cells) = 0 Then
MsgBox "Sorry: no data"
Else
With .SpecialCells(xlCellTypeConstants)
firstrow = .Areas(1).Row
lastrow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
End If
For rowcount = firstrow To firstrow + 9
Range("A" & rowcount).Value = "OP0" & rowcount - firstrow + 1
Next rowcount
For rowcount = firstrow + 9 To lastrow
Range("A" & rowcount).Value = "OP" & rowcount - firstrow + 1
Next rowcount
End With
End Sub
Appreciate any insight available.
Managed to work it out, took way longer than it should have -.-'
I borrowed a bit of Function code to reference the number from column A, then used that to reference the cells that I wanted.
For Each Nm In shNAMES
rownum = GetDigits(Nm) 'This bit here is calling the function
If Not Evaluate("ISREF('" & CStr(Nm.Text) & "'!A1)") Then
wsTEMP.Copy after:=.Sheets(.Sheets.Count)
ActiveSheet.Name = CStr(Nm.Text)
ActiveSheet.Range("A1:J1").Value = wsMASTER.Range("A" & rownum & ":J" & rownum).Value `This is utilising rownum to reference the cells
End If
Next Nm
Function code can be found here: How to find numbers from a string?
Entire code section in case it is useful to someone in future:
Option Explicit
Sub SheetsFromTemplate()
Application.ScreenUpdating = False
Rows("1:8").EntireRow.Delete
Call CreateLONums
Dim wsMASTER As Worksheet, wsTEMP As Worksheet, wsINDEX As Worksheet, wasVISIBLE As Boolean
Dim shNAMES As Range, Nm As Range
Dim firstrow As Long, lastrow As Long, rownum As Integer
With ThisWorkbook
Set wsTEMP = .Sheets("Template")
wasVISIBLE = (wsTEMP.Visible = xlSheetVisible)
If Not wasVISIBLE Then wsTEMP.Visible = xlSheetVisible
Set wsMASTER = .Sheets("Creation Page")
Set wsINDEX = .Sheets("Local Options")
With Sheets("Creation Page").Columns("A")
If WorksheetFunction.CountA(.Cells) = 0 Then
MsgBox "No Data Available"
Else
With .SpecialCells(xlCellTypeConstants)
firstrow = .Areas(1).Row
lastrow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
End If
End With
Set shNAMES = wsMASTER.Range("A" & firstrow, "A" & lastrow)
For Each Nm In shNAMES
rownum = GetDigits(Nm)
If Not Evaluate("ISREF('" & CStr(Nm.Text) & "'!A1)") Then
wsTEMP.Copy after:=.Sheets(.Sheets.Count)
ActiveSheet.Name = CStr(Nm.Text)
ActiveSheet.Range("A1:J1").Value = wsMASTER.Range("A" & rownum & ":J" & rownum).Value
End If
Next Nm
wsINDEX.Activate
If Not wasVISIBLE Then wsTEMP.Visible = xlSheetHidden
End With
Worksheets("Template").Move after:=Worksheets(Worksheets.Count)
Worksheets("Creation Page").Move after:=Worksheets(Worksheets.Count)
Call CreateLinksToAllSheets
Call UpdateIndexTechSpec
Call UpdateIndexOptDescription
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
Sub CreateLONums()
Dim firstrow As Long, lastrow As Long, rowcount As Integer
Columns("A:A").Insert Shift:=xlToRight
With Sheets("Creation Page").Columns("B")
If WorksheetFunction.CountA(.Cells) = 0 Then
MsgBox "Sorry: no data"
Else
With .SpecialCells(xlCellTypeConstants)
firstrow = .Areas(1).Row
lastrow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
End If
For rowcount = firstrow To firstrow + 9
Range("A" & rowcount).Value = "OP0" & rowcount - firstrow + 1
Next rowcount
For rowcount = firstrow + 9 To lastrow
Range("A" & rowcount).Value = "OP" & rowcount - firstrow + 1
Next rowcount
End With
End Sub
Sub CreateLinksToAllSheets()
Dim sh As Worksheet
Dim cell As Range
Sheets("Local Options").Activate
ActiveSheet.Cells(10, 1).Select
For Each sh In ActiveWorkbook.Worksheets
If ActiveSheet.Name <> sh.Name Then
ActiveCell.Hyperlinks.Add Anchor:=Selection, Address:="", SubAddress:="'" & sh.Name & "'" & "!A1", TextToDisplay:=sh.Name
ActiveCell.Offset(1, 0).Select
End If
Next sh
Call UpdateIndexTechSpec
Call UpdateIndexOptDescription
End Sub
Sub UpdateIndexTechSpec()
Dim sh As Worksheet
Dim cell As Range
Sheets("Local Options").Activate
ActiveSheet.Cells(10, 2).Select
For Each sh In ActiveWorkbook.Worksheets
If ActiveSheet.Name <> sh.Name Then
ActiveCell.Value = sh.Range("B2").Value
ActiveCell.Offset(1, 0).Select
End If
Next sh
End Sub
Sub UpdateIndexOptDescription()
Dim sh As Worksheet
Dim cell As Range
Sheets("Local Options").Activate
ActiveSheet.Cells(10, 3).Select
For Each sh In ActiveWorkbook.Worksheets
If ActiveSheet.Name <> sh.Name Then
ActiveCell.Value = sh.Range("D2").Value
ActiveCell.Offset(1, 0).Select
End If
Next sh
End Sub
Function GetDigits(ByVal s As String) As String
Dim char As String
Dim i As Integer
GetDigits = ""
For i = 1 To Len(s)
char = Mid(s, i, 1)
If char >= "0" And char <= "9" Then
GetDigits = GetDigits + char
End If
Next i
End Function

How to find duplicates in a column in excel using vba and then popup a Msgbox?

Want to find duplicates in a column in excel and want to popup a msgbox upon finding even 1 duplicate and it shouldn't keep on popping messages if it finds more than one duplicate.
Also, if i can use two column cell values and use that together to find duplicates, this would be also helpful.
Sub ColumnDuplicates()
Dim lastRow As Long
Dim matchFoundIndex As Long
Dim iCntr As Long
lastRow = Range("A65000").End(xlUp).Row
For iCntr = 1 To lastRow
If Cells(iCntr, 1) <> "" Then
matchFoundIndex = WorksheetFunction.Match(Cells(iCntr, 1), Range("A1:A" & lastRow), 0)
If iCntr <> matchFoundIndex Then
MsgBox ("There are duplicates in Column A")
End If
End If
Next
MsgBox ("No Duplicates in Column A")
End Sub
Expecting to print message saying that column A has duplicates or does not have duplicates
What about the use of EVALUATE?
Public Sub Test()
With ThisWorkbook.Sheets("Sheet1")
lr = .Cells(.Rows.Count, "A").End(xlUp).Row
If .Evaluate("=Max(countif(A1:A" & lr & ",A1:A" & lr & "))") > 1 Then
MsgBox "Duplicates!"
Else
MsgBox "No Duplicates!"
End If
End With
End Sub
Or, parameterized:
Public Sub Test(ByVal sheet As Worksheet, ByVal columnHeading As String)
With sheet
lr = .Cells(.Rows.Count, columnHeading).End(xlUp).Row
If .Evaluate("=Max(countif(" & columnHeading & "1:" & columnHeading & lr & "," & columnHeading & "1:" & columnHeading & lr & "))") > 1 Then
MsgBox "Duplicates!"
Else
MsgBox "No Duplicates!"
End If
End With
End Sub
Now you can invoke it like this:
Test Sheet1, "A" ' find dupes in ThisWorkbook/Sheet1 in column A
Test Sheet2, "B" ' find dupes in ThisWorkbook/Sheet2 in column B
Test ActiveWorkbook.Worksheets("SomeSheet"), "Z" ' find dupes in "SomeSheet" worksheet of whatever workbook is currently active, in column Z
Throw your values in a dictionary
Sub ColumnDuplicates()
Dim lastRow As Long
Dim matchFoundIndex As Long
Dim iCntr As Long
lastRow = Range("A65000").End(xlUp).Row
Set oDictionary = CreateObject("Scripting.Dictionary")
For iCntr = 1 To lastRow
If Cells(iCntr, 1) <> "" Then
If oDictionary.Exists(Cells(iCntr, 1).Value) Then
MsgBox ("There are duplicates in Column A")
Exit Sub
Else
oDictionary.Add Cells(iCntr, 1).Value, Cells(iCntr, 1).Value
End If
End If
Next
MsgBox ("No Duplicates in Column A")
End Sub
If you have Excel 2007+ then this will be faster. This code ran in 1 sec for 200k rows
Sub Sample()
Debug.Print Now
Dim ws As Worksheet
Dim wsTemp As Worksheet
Set ws = Sheet1
Set wsTemp = ThisWorkbook.Sheets.Add
ws.Columns(1).Copy wsTemp.Columns(1)
wsTemp.Columns(1).RemoveDuplicates Columns:=1, Header:=xlNo
If Application.WorksheetFunction.CountA(ws.Columns(1)) <> _
Application.WorksheetFunction.CountA(wsTemp.Columns(1)) Then
Debug.Print "There are duplicates in Col A"
Else
Debug.Print "duplicates found in Col A"
End If
Application.DisplayAlerts = False
wsTemp.Delete
Application.DisplayAlerts = True
Debug.Print Now
End Sub
I used the below code to generate 200k records in Col A
Sub GenerateSampleData()
Range("A1:A200000").Formula = "=Row()"
Range("A1:A200000").Value = Range("A1:A200000").Value
Range("A10000:A20000").Value = Range("A20000:A30000").Value
End Sub
Code execution

Deleting cells if they contain 0

Im looking to delete certain cells in all the columns on my worksheet if they contain 0 and then shift all the data upwards.
I found this formula on a different thread and it only worked for one column (column p) for some reason and only worked on half of that.
Hope you experts can help.
Option Explicit
Sub Sample()
Dim row_index As Long, lRow As Long, i As Long
Dim ws As Worksheet
Dim delRange As Range
'~~> Change this to the relevant worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
row_index = 7
Application.ScreenUpdating = False
With ws
lRow = .Range("P" & .Rows.Count).End(xlUp).Row
For i = row_index To lRow
If .Range("P" & i).Value <> "" And .Range("P" & i).Value = 0 Then
If delRange Is Nothing Then
Set delRange = .Range("P" & i)
Else
Set delRange = Union(delRange, .Range("P" & i))
End If
End If
Next
End With
If Not delRange Is Nothing Then delRange.Delete shift:=xlUp
Application.ScreenUpdating = True
End Sub
Try this:
Sub ZeroKiller()
Dim rKill As Range
Set rKill = Nothing
For Each r In ActiveSheet.UsedRange
If r.Value = 0 And r.Value <> "" Then
If rKill Is Nothing Then
Set rKill = r
Else
Set rKill = Union(rKill, r)
End If
End If
Next r
If rKill Is Nothing Then
Else
rKill.Delete Shift:=xlUp
End If
End Sub
Try the below code. The problem looks to be that your actual delete command is not in the loop, but at the end. So it only happens once. Also, Union() is unnecessary.
Sub Sample()
Dim lRow, i As Long
Dim ws As Worksheet
Dim delRange As Range
'~~> Change this to the relevant worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
Application.ScreenUpdating = False
With ws
lRow = .Range("P" & .Rows.Count).End(xlUp).Row
For i = lRow To 7 Step -1
If .Range("P" & i).Value <> "" And .Range("P" & i).Value = 0 Then
.Range("P" & i).Delete shift:=xlUp
End If
Next
End With
Application.ScreenUpdating = True
End Sub
What is up with .delete of a unioned range...?

Resources