VBA code cannot run in shared workbook - vba

Just a concern regarding shared workbooks. I have a script that moves a certain row to the appropriate sheet based on cell values.
When I copy the row, the format is usually pasted in unshared workbooks.
However, in shared workbooks, formats are completely ignored.
I can't seem the find the reason why....
Any help would be greatly appreciated.
Thanks
Sub RunScriptButton_Click()
'On Error GoTo CleanFail
If MsgBox("Run Script?", vbYesNo, "Run Script") = vbNo Then
Exit Sub
End If
'Disables screen flashing when the information is updated
Application.ScreenUpdating = False
Dim project As String, ws As Worksheet, ignoredSheets As Object, scheduleSheets As Object
Dim legendSht As Worksheet, masterSht As Worksheet
Dim i As Integer, j As Integer, k As Integer, x As Integer, y As Integer, z As Integer
Dim lastrow As Integer, lastcoln As Integer, lastrow2 As Integer, lastrow3 As Integer, lastRowLegend As Integer
Dim rowht As Double, rowht2 As Double
Dim count As Integer, SAcount As Integer
Dim ID As String, name As Range, allppl As Range, allppl2 As Range
Dim month_col As Range, month_col_no As Integer, next_month_col As Range, next_month As Integer
Dim mcount1 As Integer, mcount2 As Integer, first As Integer, secnd As Integer
Dim monthrow As Integer, script_info_row As Integer, proj_coln As Integer, name_coln As Integer, assist_coln As Integer
Set legendSht = ThisWorkbook.Worksheets("Legend")
Set masterSht = ThisWorkbook.Worksheets("Master Schedule")
'----------------------------------------------------------
' Set the worksheet names to be ignored by the script (non-schedule sheets)
' Add additional exceptions by adding a new item to the dictionary with "Sheet Name", [next number]
Set ignoredSheets = CreateObject("Scripting.Dictionary")
ignoredSheets.Add "Legend", 1
ignoredSheets.Add "Master Schedule", 2
ignoredSheets.Add "Surveyor Overview", 3
'----------------------------------------------------------
lastRowLegend = legendSht.UsedRange.Row - 1 + legendSht.UsedRange.Rows.count
script_info_row = legendSht.Range(legendSht.Cells(1, 1), legendSht.Cells(lastRowLegend, 1)).Find(what:="Script Information").Row + 1
With masterSht
'Find last row with data on the master schedule sheet
Set tempRange = .Cells(.Rows.count, "B").End(xlUp)
lastrow = tempRange.Row
'Find last column with data on the master schedule sheet
If .Cells(2, .Columns.count) <> vbNullString Then
Set tempRange = .Cells(2, .Columns.count)
lastcoln = tempRange.Column
Else
Set tempRange = .Cells(2, .Columns.count).End(xlToLeft)
lastcoln = tempRange.Column
End If
proj_coln = .Range(.Cells(1, 1), .Cells(1, lastcoln)).Find(what:="Project").Column
name_coln = .Range(.Cells(1, 1), .Cells(1, lastcoln)).Find(what:="Name").Column
assist_coln = .Range(.Cells(1, 1), .Cells(1, lastcoln)).Find(what:="Assistant").Column
'startCol = .Range(.Cells(1, 1), .Cells(1, lastcoln)).Find(what:=CStr(lastUpdateDate + 1)).Column
'endCol = .Range(.Cells(1, 1), .Cells(1, lastcoln)).Find(what:=CStr(currentDate)).Column
End With
Set scheduleSheets = CreateObject("Scripting.Dictionary")
'Loops through each worksheet except for legend and master schedule worksheet and deletes all information
For Each ws In ThisWorkbook.Worksheets
If Not ignoredSheets.Exists(ws.name) Then
ws.Cells.Delete
'Repositions buttons that get shoved off the page?
'For Each Control In ws.Shapes
' If Control.Type = msoOLEControlObject Then
' Control.Top = 48
' Control.Left = 9.75
' End If
'Next Control
'MsgBox Mid(ws.name, InStr(ws.name, "(") + 1, InStr(ws.name, ")") - InStr(ws.name, "(") - 1)
scheduleSheets.Add Mid(ws.name, InStr(ws.name, "(") + 1, InStr(ws.name, ")") - InStr(ws.name, "(") - 1), ws.Index
End If
Next ws
'copies the headers and dates from master schedule sheet
With masterSht
.Range(.Cells(1, 1), .Cells(2, lastcoln)).Copy
rowht = .Rows(1).RowHeight
rowht2 = .Rows(2).RowHeight
End With
'pastes the copied headers into every sheet except for ignored sheets
For Each ws In ThisWorkbook.Worksheets
If Not ignoredSheets.Exists(ws.name) Then
With ws
.Range("A1").PasteSpecial xlPasteColumnWidths
.Range("A1").PasteSpecial xlPasteFormats
.Range("A1").PasteSpecial xlPasteValuesAndNumberFormats
.Rows(1).RowHeight = rowht
.Rows(2).RowHeight = rowht2
End With
End If
Next ws
'Checks number in Project column of Master Schedule and copies row into sheet with matching number between brackets in sheet name
For i = 3 To lastrow
project = masterSht.Cells(i, proj_coln)
'Loop through stored sheet project numbers and compare to current row to find the correct sheet to copy to
For Each strKey In scheduleSheets.Keys()
If InStr(project, strKey) <> 0 Then
masterSht.Range(masterSht.Cells(i, 1), masterSht.Cells(i, lastcoln)).Copy
ThisWorkbook.Sheets(scheduleSheets.Item(strKey)).Range("A" & i).PasteSpecial xlPasteColumnWidths
ThisWorkbook.Sheets(scheduleSheets.Item(strKey)).Range("A" & i).PasteSpecial xlPasteFormats
ThisWorkbook.Sheets(scheduleSheets.Item(strKey)).Range("A" & i).PasteSpecial xlPasteValuesAndNumberFormats
ThisWorkbook.Sheets(scheduleSheets.Item(strKey)).Range("A" & i).PasteSpecial xlPasteComments
'If only one project number in this item, then break out of looping through sheet names and go to next row in schedule
If InStr(project, "/") = 0 Then
Exit For
End If
End If
Next
Next i
'Deletes empty rows in sheets other than legend and master schedule
For Each ws In ThisWorkbook.Worksheets
If Not ignoredSheets.Exists(ws.name) Then
ws.Cells.EntireColumn.Hidden = False
With ws.UsedRange
For j = .Rows.count To 3 Step -1
If Application.WorksheetFunction.CountA(.Rows(j).EntireRow) = 0 Then
.Rows(j).EntireRow.Delete
End If
Next j
End With
lastrow = ws.UsedRange.Rows.count
'Count the number of survey assistants in each project worksheet
SAcount = Application.WorksheetFunction.CountIfs(ws.Range(ws.Cells(3, name_coln), ws.Cells(lastrow, name_coln)), "SA:*")
'Crew count labels
ws.Range("A" & lastrow + 1) = "Total Crew Count: " & lastrow - 2 - SAcount
ws.Range("E" & lastrow + 2) = "Double Crew Count"
ws.Range("E" & lastrow + 3) = "Single Crew Count"
'Get total crew count by counting number of party chiefs (hide SAs)
Set allppl = ws.Range(ws.Cells(3, name_coln), ws.Cells(lastrow, name_coln))
For Each name In allppl
If Left(name, 3) = "SA:" Then
name.EntireRow.Hidden = True
End If
Next name
'Tally active crews for each day
For j = assist_coln To lastcoln
'Find 3 letter code for current project sheet
ID = Application.WorksheetFunction.Index(Sheet1.Range(Sheet1.Cells(script_info_row, 1), Sheet1.Cells(lastRowLegend, 3)), _
Application.WorksheetFunction.Match(ws.name, Sheet1.Range(Sheet1.Cells(script_info_row, 1), Sheet1.Cells(lastRowLegend, 1)), 0), 2)
'Count number of active crews for the current day
count = COUNTIFv(ws.Range(ws.Cells(2, j), ws.Cells(lastrow, j)), "*" & ID & "*")
ws.Cells(lastrow + 1, j).Value = count
Next j
'Unhide all cells
ws.Cells.EntireRow.Hidden = False
'Hide all crew except survey assistants to determine number of 2-man crews
If lastrow - 2 - SAcount > 0 Then
For Each name In allppl
If Left(name, 3) <> "SA:" Then
name.EntireRow.Hidden = True
End If
Next name
End If
'Tally active 2-man crews for each day
For j = assist_coln To lastcoln
'ID = Application.WorksheetFunction.Index(Sheet1.Range(Sheet1.Cells(script_info_row, 1), Sheet1.Cells(lastRowLegend, 3)), _
'Application.WorksheetFunction.Match(ws.name, Sheet1.Range(Sheet1.Cells(script_info_row, 1), Sheet1.Cells(lastRowLegend, 1)), 0), 2)
count2 = COUNTIFv(ws.Range(ws.Cells(2, j), ws.Cells(lastrow, j)), "*" & ID & "*")
ws.Cells(lastrow + 2, j).Value = count2 'Active two-man crews for current date
ws.Cells(lastrow + 3, j).Value = ws.Cells(lastrow + 1, j) - count2 'One-man crew = Total crew - 2M crew
Next j
ws.Cells.EntireRow.Hidden = False
'Hide all schedule columns prior to current day
month_col_no = ws.Range(ws.Cells(2, 1), ws.Cells(2, lastcoln)).Find(what:=Format(Now, "m/d/yyyy")).Column
ws.Range(ws.Cells(1, assist_coln), ws.Cells(1, month_col_no - 1)).EntireColumn.Hidden = True
ws.Activate
ActiveWindow.ScrollRow = 1
'Tabulate monthly crew counts
lastrow3 = ws.UsedRange.Rows.count
monthrow = lastrow3 + 1
For i = Month(Date) To 12
month_col_no = ws.Range(ws.Cells(2, 1), ws.Cells(2, lastcoln)).Find(what:=i & "/1/" & Year(Date)).Column
If i <> 12 Then
next_month = ws.Range(ws.Cells(2, 1), ws.Cells(2, lastcoln)).Find(what:=i + 1 & "/1/" & Year(Date)).Column
Else
next_month = ws.Range(ws.Cells(2, 1), ws.Cells(2, lastcoln)).Find(what:="12/31/" & Year(Date)).Column + 1
End If
mcount1 = Application.Sum(ws.Range(ws.Cells(lastrow3 - 1, month_col_no), ws.Cells(lastrow3 - 1, next_month - 1)))
mcount2 = Application.Sum(ws.Range(ws.Cells(lastrow3, month_col_no), ws.Cells(lastrow3, next_month - 1)))
ws.Cells(monthrow, 1) = MonthName(i) & " Double Crew Total: " & mcount1
ws.Cells(monthrow + 1, 1) = MonthName(i) & " Single Crew Total: " & mcount2
monthrow = monthrow + 2
Next i
End If
Next ws
With masterSht
.Activate
ActiveWindow.ScrollRow = 1
month_col_no = .Range(.Cells(2, 1), .Cells(2, lastcoln)).Find(what:=Month(Date) & "/" & Day(Date) & "/" & Year(Date)).Column
.Range(.Cells(1, assist_coln + 1), .Cells(1, month_col_no - 1)).EntireColumn.Hidden = True
End With
'enables screen flash and auto calculation again
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
'CleanExit:
'Cleanup code
MsgBox "Process complete"
' Exit Sub
'CleanFail:
' Raise Err.Number
' Resume CleanExit
' Resume
End Sub

Shared workbooks have limitations. The biggest one is that they can become corrupt at any time and are impossible to troubleshoot because their behaviour is not consistent.
Avoid shared workbooks.

Related

Excel VBA copy from one sheet to other sheets specific cells based on criteria

I am trying to copy from Sheet1, specific rows when on that row a specific cell has status "DONE" selected to say, and a second criteria after "DONE" is to check if on the same row, another cell has also a specific value. After that, copy the rows found each on specific sheet, checking destination if duplicates are found.
I have managed until now to copy from Sheet1 to the other based on the 2 criteria (old school with IF, I tried with autofilter but I didn't manage to do it) but I am having a hard time preventing duplicates to be copied to the other sheets.
I tried everything, value checking based on first sheet with Range, writing a macro for each sheet so it prevents duplicates, nothing worked and i am stuck on this.
Another problem with below code is that after hitting Update button multiple times, it doesn't duplicate all found rows, but only the first one found, and also inserts some empty rows in between and I don't understand the reason for that.
Here is the code:
Private Sub CommandButton1_Click()
Dim LastRow As Long
Dim i As Long, j As Long, k As Long, j1 As Long, k1 As Long, j_last As Long,
k_last As Long
Dim a As Long, b As Long
Dim ActiveCell As String
With Worksheets("PDI details")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
With Worksheets("Demo ATMC")
j = .Cells(.Rows.Count, "A").End(xlUp).Row + 2
End With
With Worksheets("Demo ATMC Courtesy")
k = .Cells(.Rows.Count, "A").End(xlUp).Row + 2
End With
With Worksheets("Demo SHJ")
j1 = .Cells(.Rows.Count, "A").End(xlUp).Row
k1 = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
With Worksheets("Demo AD")
a = .Cells(.Rows.Count, "A").End(xlUp).Row
b = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
MsgBox (j)
For i = 5 To LastRow
With Worksheets("PDI details")
If .Cells(i, 20).Value <> "" Then
If .Cells(i, 20).Value = "DONE" Then
If .Cells(i, 11).Value = "ATMC DEMO" Then
If Not .Cells(i, 7) = Worksheets("Demo ATMC").Range("D4") Then
Worksheets("Demo ATMC").Range("A" & j) = Worksheets("PDI details").Range("A" & i).Value
Worksheets("Demo ATMC").Range("B" & j) = Worksheets("PDI details").Range("E" & i).Value
Worksheets("Demo ATMC").Range("C" & j) = Worksheets("PDI details").Range("F" & i).Value
Worksheets("Demo ATMC").Range("D" & j) = Worksheets("PDI details").Range("G" & i).Value
Worksheets("Demo ATMC").Range("F" & j) = Worksheets("PDI details").Range("H" & i).Value
Worksheets("Demo ATMC").Range("G" & j) = Worksheets("PDI details").Range("I" & i).Value
End If
End If
If .Cells(i, 11).Value = "ATMC COURTESY" Then
If Not .Cells(i, 7) = Worksheets("Demo ATMC Courtesy").Range("D4")
Then
Worksheets("Demo ATMC Courtesy").Range("A" & k) = Worksheets("PDI details").Range("A" & i).Value
Worksheets("Demo ATMC Courtesy").Range("B" & k) = Worksheets("PDI details").Range("E" & i).Value
Worksheets("Demo ATMC Courtesy").Range("C" & k) = Worksheets("PDI details").Range("F" & i).Value
Worksheets("Demo ATMC Courtesy").Range("D" & k) = Worksheets("PDI details").Range("G" & i).Value
Worksheets("Demo ATMC Courtesy").Range("F" & k) = Worksheets("PDI details").Range("H" & i).Value
Worksheets("Demo ATMC Courtesy").Range("G" & k) = Worksheets("PDI details").Range("I" & i).Value
k = k + 1
End If
End If
End If
End If
End With
Next i
End Sub
I couldn't test the code suggested below but I believe that it does what you wish it to do.
Option Explicit
Private Sub CommandButton1_Click()
' 23 Dec 2017
Dim WsPdi As Worksheet
Dim WsAtmc As Worksheet, WsCourtesy As Worksheet
Dim R As Long, Rl As Long ' row / lastrow "PDI details"
Set WsPdi = Worksheets("PDI Detail")
Set WsAtmc = Worksheets("Demo ATMC")
Set WsCourtesy = Worksheets("Demo ATMC Courtesy")
Application.ScreenUpdating = False
With WsPdi
Rl = .Cells(.Rows.Count, "A").End(xlUp).Row
For R = 5 To Rl
If .Cells(R, 20).Value = "DONE" Then
Select Case .Cells(R, 11).Value
Case "ATMC DEMO"
TransferData WsPdi, WsAtmc, R
Case "ATMC COURTESY"
TransferData WsPdi, WsCourtesy, R
End Select
End If
Next R
End With
Application.ScreenUpdating = True
End Sub
Private Sub TransferData(WsSource As Worksheet, _
WsDest As Worksheet, _
R As Long)
' 23 Dec 2017
Dim Csource() As String
Dim Rn As Long ' next empty row in WsDest
Dim C As Long
Csource = Split(",A,E,F,G,,H,R", ",")
With WsDest
If WsSource.Cells(R, 7).Value <> .Cells(4, "D").Value Then
Rn = .Cells(.LastRow, "A").End(xlUp).Row + 1
For C = 1 To 7 ' columns A to G
If C <> 5 Then
.Cells(Rn, C).Value = WsSource.Cells(R, Csource(C)).Value
End If
Next C
End If
End With
End Sub

How to copy top and remaining values to different cell in Excel VBA

I would like to select the top 10 Values from Main table and paste in different range (E3) and REMAINING all values and PASTE and SUM in another range(I3)
I used the code below. It is working for Top 10 values, BUT for remaining,
when i am adding one more row in Main table, it is not working. Help me.
Private Sub CommandButton1_Click()
'Calculation for Top 10 Countries
Sheets("Sheet1").Range("C" & Rows.Count).End(xlUp).Offset(-9, -2).Resize(10, 3).Copy Sheets("Sheet1").Range("E3")
'Calculation for Remaining Countries
Range("A3:C14").Copy Range("I3")
Range("I15").Select
ActiveCell.FormulaR1C1 = "Remaining"
Range("J15").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
Range("K15").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
End Sub
Please try this code.
Option Explicit
Private Sub CommandButton1_Click()
Dim Rng As Range
Dim R As Long, Rl As Long ' last row
With Worksheets("Sheet1")
Rl = .Cells(.Rows.Count, "A").End(xlUp).Row
R = Application.Max(Rl - 9, 3)
' pick Top 10 Countries
Set Rng = Range(.Cells(R, "A"), .Cells(Rl, "C"))
Rng.Copy Destination:=.Cells(3, "E")
' pick remaining countries
If R > 3 Then
Set Rng = Range(.Cells(3, "A"), .Cells(R - 1, "C"))
Rng.Copy Destination:=.Cells(3, "I")
' write totals
Rl = .Cells(.Rows.Count, "I").End(xlUp).Row
Set Rng = Range(.Cells(3, "J"), .Cells(Rl, "J"))
Rl = Rl + 1
With .Cells(Rl, "I")
.Value = "Remaining"
.HorizontalAlignment = xlRight
.Font.Bold = True
End With
With .Cells(Rl, "J")
.Value = Application.Sum(Rng)
.HorizontalAlignment = xlCenter
.Font.Bold = True
End With
With .Cells(Rl, "K")
.Value = Application.Sum(Rng.Offset(0, 1))
.HorizontalAlignment = xlCenter
.Font.Bold = True
End With
End If
End With
End Sub
Private Sub CommandButton1_Click()
Dim lr As Long
Dim pr As Long
With Sheets("Sheet1")
lr = .Range("A" & Rows.Count).End(xlUp).Row
'Calculation for Top 10 Countries
.Range("A" & (lr - 10) & ":C" & lr).Copy .Range("E3")
'Calculation for Remaining Countries
.Range("A3:C" & (lr - 11)).Copy .Range("I3")
pr = .Range("A3:C" & (lr - 11)).Rows.Count + 3
.Range("I" & pr).Value = "Remaining"
.Range("J" & pr).FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
.Range("K" & pr).FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
End With
End Sub
The code below first sorts the main table by country (hard-coded option), then creates 2 copies of the sorted table in columns E and I sorted descending or ascending (hard-coded option), and deletes the part in each which isn't needed. The user can choose the year by which the ranking is to be determined, and the program allows for more years to be added to the main table and processed without change of code. You may wish to change the name of the worksheet in the code.
Option Explicit
Private Sub CommandButton1_Click()
' 14 Nov 2017
Dim Ws As Worksheet
Dim ClmCount As Long
Dim SortYear As Long ' = column in main table
Dim SortOrder As XlSortOrder
Dim Rng As Range
Dim Rl As Long, Rend As Long ' last row
Dim R As Long, C As Long
Set Ws = Worksheets("Deepak")
SortYear = YearToSort(Ws)
If SortYear Then ' exit if cancelled
Application.ScreenUpdating = False
With Ws
Rl = .Cells(.Rows.Count, "A").End(xlUp).Row
If Rl > 3 Then ' skip, if no list
ClmCount = MainTableColumnsCount(Ws)
' === Sorting the main table
' you can sort on another column, in another order
' or skip the sort entirely
' to skip remove these two lines of code
Set Rng = Range(.Cells(3, "A"), .Cells(Rl, ClmCount))
SortRange Rng, 1, xlAscending ' sort Main by country (column 1)
' === set the sort order for Lists 1 & 2 here:-
SortOrder = xlAscending ' Alt: change for xlDescending
SheetSetup Ws, SortYear, SortOrder ' create sorted copies
' List 1: delete all but the top 10
C = ClmCount + 2
R = IIf(SortOrder = xlAscending, 3, 13)
Rend = IIf(SortOrder = xlAscending, Rl - 10, Rl)
Set Rng = Range(.Cells(R, C), .Cells(Rend, C + ClmCount - 1))
Rng.Delete Shift:=xlUp
' List 2: delete the top 10
C = 2 * (ClmCount + 1) + 1
R = IIf(SortOrder = xlAscending, Rl - 9, 3)
Rend = IIf(SortOrder = xlAscending, Rl, 12)
Set Rng = Range(.Cells(R, C), .Cells(Rend, C + ClmCount - 1))
Rng.Delete Shift:=xlUp
' write totals
Rl = .Cells(.Rows.Count, C).End(xlUp).Row
With .Cells(Rl + 1, C)
.Value = "Remaining"
.HorizontalAlignment = xlRight
.Font.Bold = True
End With
For C = (C + 1) To (C + ClmCount - 1)
Set Rng = Range(.Cells(3, C), .Cells(Rl, C))
With .Cells(Rl + 1, C)
.Value = Application.Sum(Rng)
.HorizontalAlignment = xlCenter
.Font.Bold = True
End With
Next C
End If
End With
Application.ScreenUpdating = True
End If
End Sub
Private Sub SortRange(Rng As Range, _
SortColumn As Long, _
SortOrder As XlSortOrder)
' 14 Nov 2017
With Rng.Worksheet.Sort
.SortFields.Clear
.SortFields.Add Key:=Rng.Columns(SortColumn), _
SortOn:=xlSortOnValues, _
Order:=SortOrder, _
DataOption:=xlSortNormal
.SetRange Rng
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Private Function YearToSort(Ws As Worksheet) As Long
' 14 Nov 2017
' return the column number of the main table
' return 0 if cancelled
Dim Fun As Long
Dim Rng As Range
Dim UserInput As String
Set Rng = Ws.Range(Cells(2, 2), Cells(2, MainTableColumnsCount(Ws)))
' use B2 as default
Do
UserInput = InputBox("Enter the year to sort by:", _
"Select a year", Rng.Cells(1).Value)
If UserInput = "" Then Exit Do
On Error Resume Next
Fun = Application.Match(CLng(UserInput), Rng, 0)
If Err Then
MsgBox "There is no data for year " & UserInput & "." & vbCr & _
"Please enter an available year.", _
vbInformation, "Invalid input"
UserInput = ""
On Error GoTo 0
End If
Loop While UserInput = ""
YearToSort = Fun + 1
End Function
Private Sub SheetSetup(Ws As Worksheet, _
SortYear As Long, _
SortOrder As XlSortOrder)
' 14 Nov 2017
Const Captions As String = "Top 10 countries,All other countries"
Dim Rng As Range, SortRng As Range
Dim ClmCount As Long
Dim Rl As Long ' last row
Dim C As Long
Dim i As Long
With Ws
Rl = .Cells(.Rows.Count, 1).End(xlUp).Row
ClmCount = MainTableColumnsCount(Ws)
C = .UsedRange.Columns.Count
If C > ClmCount Then
' delete all existing except main table
Set Rng = Range(.Cells(1, ClmCount + 1), .Cells(Rl, C))
Rng.Cells.Delete Shift:=xlUp
End If
Set Rng = Range(.Cells(1, 1), .Cells(Rl, ClmCount))
' create two copies of the main table
For i = 1 To 2
C = (ClmCount + 1) * i + 1
Rng.Copy Destination:=.Cells(1, C)
.Cells(1, C + 1).Value = Split(Captions, ",")(i - 1)
Set SortRng = Range(.Cells(3, C), .Cells(Rl, C + ClmCount - 1))
SortRange SortRng, SortYear, SortOrder
Next i
End With
End Sub
Private Function MainTableColumnsCount(Ws As Worksheet) As Long
' 14 Nov 2017
Dim Fun As Long
Do
Fun = Fun + 1
Loop While Len(Ws.Cells(2, Fun).Value)
MainTableColumnsCount = Fun - 1
End Function
You could do it with formulas as well. In the G3 cell type:
=LARGE(C$3:C$200, ROW()-2)
for finding the top country. In F3 and E3 insert:
=INDEX(B$3:B$200, MATCH(G3,C$3:C$200,0))
and
=INDEX(A$3:A$200, MATCH(G3,C$3:C$200,0))
respectively. Then drag then down to 12th row. You have top 10 now. In the cell K3 type:
=IFERROR(LARGE(C$3:C$200, ROW()+8),"")
and in J3 and I3:
=IFERROR(INDEX(B$3:B$200, MATCH(K3,C$3:C$200,0)),"")
and
=IFERROR(INDEX(A$3:A$200, MATCH(K3,C$3:C$200,0)),"")
Then drag then down several screens. There is less than 200 countries and territories on the world, so it should be enough. If there is no macros, you don'tneed the button, everything will update on fly.

Excel macro: Combine rows if column match

I want to be able to combine the rows for which the value in the first column matches, so that the values of non-blank cells are consolidated into one row. E.g.:
Mary Smith, A, [blank cell]
Mary Smith, [blank cell], B
-->
Mary Smith A B
I've tried to use the code below:
Dim RowNum As Long, LastRow As Long
Application.ScreenUpdating = False
RowNum = 4
LastRow = Cells.SpecialCells(xlCellTypeLastCell).Row
Range("A4", Cells(LastRow, 13)).Select
For Each Row In Selection
With Cells
If Cells(RowNum, 1) = Cells(RowNum + 1, 1) Then
Cells(RowNum + 1, 1).Copy Destination:=Cells(RowNum, 1)
Cells(RowNum + 1, 2).Copy Destination:=Cells(RowNum, 2)
Cells(RowNum + 1, 3).Copy Destination:=Cells(RowNum, 3)
Cells(RowNum + 1, 4).Copy Destination:=Cells(RowNum, 4)
Cells(RowNum + 1, 5).Copy Destination:=Cells(RowNum, 5)
Cells(RowNum + 1, 6).Copy Destination:=Cells(RowNum, 6)
Cells(RowNum + 1, 7).Copy Destination:=Cells(RowNum, 7)
Cells(RowNum + 1, 8).Copy Destination:=Cells(RowNum, 8)
Cells(RowNum + 1, 9).Copy Destination:=Cells(RowNum, 9)
Cells(RowNum + 1, 10).Copy Destination:=Cells(RowNum, 10)
Cells(RowNum + 1, 11).Copy Destination:=Cells(RowNum, 11)
Cells(RowNum + 1, 12).Copy Destination:=Cells(RowNum, 12)
Cells(RowNum + 1, 13).Copy Destination:=Cells(RowNum, 13)
Rows(RowNum + 1).EntireRow.Delete
End If
End With
RowNum = RowNum + 1
Next Row
Application.ScreenUpdating = True
'
End Sub
This does a fine job of consolidating the data so that there are only unique values in the first column, HOWEVER, when the row is copied up, the values of blank cells copy over populated cells, which NOT what I want. So for instance, running this macro on the above data would yield:
Mary Smith, A, [blank cell]
Mary Smith, [blank cell], B
-->
Mary Smith, A, [blank cell]
Any insight into how I might modify the above code (or use something more elegant) would be appreciated!!
This will do it very quickly:
Sub foo()
Dim ws As Worksheet
Dim lstrow As Long
Set ws = Sheets("Sheet1") ' Change to your sheet
With ws
lstrow = .Range("A" & .Rows.Count).End(xlUp).Row
With .Range("B4:M" & lstrow)
.Offset(, 26).FormulaR1C1 = "=IFERROR(INDEX(R4C[-26]:R" & lstrow & "C[-26],MATCH(1,INDEX((R4C1:R" & lstrow & "C1 = RC1)*(R4C[-26]:R" & lstrow & "C[-26] <>""""),),0)),"""")"
ws.Calculate
.Value = .Offset(, 26).Value
.Offset(, 26).ClearContents
End With
With .Range("A4:M" & lstrow)
.Value = .Value
.RemoveDuplicates 1, xlGuess
End With
End With
End Sub
It basically uses the formula: =INDEX(B$4:B$4,MATCH(1,INDEX(($A$4:$A$4 = $A4)*(B$4:B$4 <>""),),0)) To find all the values. Puts those formulas in blank columns and then copies the data back and removes the duplicates.
This will do all 13 columns at once.
It also does not care how many times the value in Column A is repeated. There could be 4 Mary Smiths in that column. It will grab the first value in each column and use that.
Before:
After:
Try the below code
Sub test()
LastRow = Range("A" & Rows.Count).End(xlUp).Row
For i = 4 To LastRow
If ((Range("A" & i).Value = Range("A" & i + 1).Value) And (Range("B" & i).Value <> Range("B" & i + 1).Value) And ((Range("B" & i).Value = "") Or (Range("B" & i + 1).Value = "")) And (Range("C" & i).Value <> Range("C" & i + 1).Value) And ((Range("C" & i).Value = "") Or (Range("C" & i + 1).Value = ""))) Then
If Range("B" & i).Value = "" Then
Range("B" & i).Value = Range("B" & i + 1).Value
ElseIf Range("B" & i + 1).Value = "" Then
Range("B" & i + 1).Value = Range("B" & i).Value
End If
If Range("C" & i).Value = "" Then
Range("C" & i).Value = Range("C" & i + 1).Value
ElseIf Range("C" & i + 1).Value = "" Then
Range("C" & i + 1).Value = Range("C" & i).Value
End If
End If
Range("B" & i).EntireRow.Delete Shift:=(xlUp)
LastRow = LastRow - 1
Next i
End Sub
Here is another approach.
Create a Personnel object. Each Personnel object can have multiple attributes (the non blank column entries in your original table).
By using the Key property of the collection object, and using the Name (column1 data) as the key, we can detect duplicates without having to sort the original data. And the number of attributes for each name is limited only by the size of the worksheet.
Other information is in the comments.
Insert a class object and rename it cPersonnel
Below is the code for the Class and Regular modules
Class Module
Option Explicit
Private pName As String
Private pAttrib As String
Private pAttribs As Collection
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
Public Property Get Attrib() As String
Attrib = pAttrib
End Property
Public Property Let Attrib(Value As String)
pAttrib = Value
End Property
Public Property Get AttribS() As Collection
Set AttribS = pAttribs
End Property
Public Function ADDAttribS(Value As String)
pAttribs.Add Value
End Function
Private Sub Class_Initialize()
Set pAttribs = New Collection
End Sub
Regular Module
Option Explicit
Sub PersonnelAttribs()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes() As Variant
Dim cP As cPersonnel, colP As Collection
Dim LastRow As Long, LastCol As Long
Dim I As Long, J As Long
'Set source and results worksheets, ranges
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 1)
With wsSrc.Cells
LastRow = .Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas, _
searchorder:=xlByRows, searchdirection:=xlPrevious).Row
LastCol = .Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas, _
searchorder:=xlByColumns, searchdirection:=xlPrevious).Column
End With
'Read source data into array
With wsSrc
vSrc = Range(.Cells(1, 1), .Cells(LastRow, LastCol))
End With
'create and collect the Personnel objects
'Source data does not need to be sorted
Set colP = New Collection
On Error Resume Next
For I = 1 To UBound(vSrc, 1)
If Trim(vSrc(I, 1)) <> "" Then
Set cP = New cPersonnel
With cP
.Name = vSrc(I, 1)
For J = 2 To UBound(vSrc, 2)
If Trim(vSrc(I, J)) <> "" Then
.Attrib = Trim(vSrc(I, J))
.ADDAttribS .Attrib
End If
Next J
colP.Add cP, .Name
Select Case Err.Number
Case 457 'duplicate name
Err.Clear
For J = 1 To .AttribS.Count
colP(.Name).ADDAttribS .AttribS(J)
Next J
Case Is <> 0
Debug.Print Err.Number, Err.Description
Stop
End Select
End With
End If
Next I
On Error GoTo 0
'Create results array
'Number of columns
For I = 1 To colP.Count
With colP(I)
J = IIf(J > .AttribS.Count, J, .AttribS.Count)
End With
Next I
ReDim vRes(0 To colP.Count, 0 To J)
'Headers
vRes(0, 0) = "Name"
For J = 1 To UBound(vRes, 2)
vRes(0, J) = "Attrib " & J
Next J
'Populate data
For I = 1 To colP.Count
With colP(I)
vRes(I, 0) = .Name
For J = 1 To .AttribS.Count
vRes(I, J) = .AttribS(J)
Next J
End With
Next I
'Clear old data and write new
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2) + 1)
With rRes
.EntireColumn.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
End Sub
Original Data
Results after Macro

Insert row in excel with a value in a specific cell

I'm using this script to insert fill with rows where non-sequential is produced in a column of an excel file.
Sub InsertValueBetween()
Dim lastrow As Long
Dim gap As Long
Dim i As Long, ii As Long
Application.ScreenUpdating = False
With ActiveSheet
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = lastrow To 3 Step -1
gap = .Cells(i, "A").Value - .Cells(i - 1, "A").Value
If gap > 1 Then
.Rows(i).Resize(gap - 1).Insert
End If
Next i
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
.Cells(3, "A").Value = .Cells(2, "A").Value + 1
.Cells(2, "A").Resize(2).AutoFill .Cells(2, "A").Resize(lastrow - 1)
End With
End Sub
In addition to adding these new rows I want them to also have a specific value in column B. I'm trying to implement this but with no result.
Anybody could help me?
One way you could tackle this challenge is with a Range variable. Here is some heavily-commented code that walks through the process:
Sub InsertValueBetweenRev2()
Dim Target As Range '<~ declare the range variable
'... declare your other variables
'... do other stuff
For i = lastrow To 3 Step -1
gap = .Cells(i, "A").Value - .Cells(i - 1, "A").Value
If gap > 1 Then
.Rows(i).Resize(gap - 1).Insert
'the next line sets the range variable to the recently
'added cells in column B
Set Target = .Range(.Cells(i, 2), .Cells(i + gap - 2, 2))
Target.Value = "Cool" '<~ this line writes text "Cool" into those cells
End If
Next i
'... the rest of your code
End Sub
So, to sum it up, we know that gap - 1 rows are going to be added, and we know that the new rows are added starting at row i. Using that knowledge, we assign the just-added cells in column B to a Range then set the .value of that Range to whatever is needed.
a Better way of doing it with less variables and faster:
Sub InsRowWithText()
Dim LR As Long, i As Long
LR = Range("D" & Rows.Count).End(xlUp).row
For i = LR To 3 Step -1
If Range("D" & i).Value <> Range("D" & i - 1).Value Then
Rows(i).Resize(1).Insert
Range("D" & i).Value = "Test"
End If
Next i
End Sub
This is how i utilized it:
Sub InsRowWithText()
Dim strMsg As String, strTitle As String
Dim LR As Long, i As Long
Text = "ADD"
strMsg = "Warning: This is a Advanced Function, Continue? "
strTitle = "Warning: Activated Advanced Function "
If MsgBox(strMsg, vbQuestion + vbYesNo, strTitle) = vbNo Then
Exit Sub
Else
Sheets("SAP Output DATA").Select
If Range("D3").Value = Text Then
MsgBox "Detected That This Step Was Already Completed, Exiting."
Exit Sub
End If
application.ScreenUpdating = False
LR = Range("D" & Rows.Count).End(xlUp).row
For i = LR To 3 Step -1
If Range("D" & i).Value <> Range("D" & i - 1).Value Then
Rows(i).Resize(1).Insert
Range("D" & i).EntireRow.Interior.ColorIndex = xlColorIndexNone
Range(("A" & i), ("D" & i)).Value = Text
End If
Next i
End If
Range("D2").Select
Selection.End(xlDown).Select
ActiveCell.Offset(1).Select
Range(("A" & ActiveCell.row), ("D" & ActiveCell.row)).Value = Text 'last row doesnt get text for some reason.
ActiveCell.EntireRow.Interior.ColorIndex = xlColorIndexNone
ActiveCell.Offset(1).Select
Range(("D" & ActiveCell.row), ("E" & ActiveCell.row)).Interior.ColorIndex = 17 'purple
application.ScreenUpdating = True
Range("D3").Select
End Sub

How do I loop through workbooks performing the same function in each?

I've been trying to create a macro that extracts specific cell data from several open workbooks that all contain a specific sheet named ("Report_Final")
Currently, my macro goes sth like this:
Sub PerLineItem()
'Main function i'm trying to call for each open workbook
Dim wb As Workbook
Dim ws, ws2 As Worksheet
Dim i, j, k, x, rng As Integer
Dim temp_total As Double
Dim mat_name1, mat_name2 As String
i = 2
j = 2
k = 2
rng = 0
Application.ScreenUpdating = False
Set wb = ActiveWorkbook
Sheets.Add
Set ws = ActiveSheet
'Intermediate sheet to filter only columns 2, 11 & 18'
ws.Name = "Report"
Cells(1, 2) = "WBS"
Cells(1, 3) = "Material"
Cells(1, 4) = "Sell Total Price"
Sheets("zero250").Select
Do While Cells(i, 2) <> ""
rng = rng + 1
i = i + 1
Loop
'Copy and paste columns 2, 11, 18 to 2, 3, 4 in the new sheet("Report")
Do While j < rng
If ((Right(Cells(j, 2), 3) = "RTN") Or (Right(Cells(j, 2), 3) = "NRT")) Then
Union(Cells(j, 2), Cells(j, 11), Cells(j, 18)).Copy
Sheets("Report").Select
Union(Cells(k, 2), Cells(k, 3), Cells(k, 3)).Select
ActiveSheet.Paste
Sheets("zero250").Select
k = k + 1
End If
j = j + 1
Loop
'Create new sheet to group up identical named materials and sum the value up
Sheets.Add
Set ws2 = ActiveSheet
'The debugger always points to the below line "name is already taken" since it is being run in the same workbook
ws2.Name = "Report_Final"
Sheets("Report").Select
i = 2
j = 2
k = 2
x = 2
rng = 1
Do While Cells(i, 2) <> ""
rng = rng + 1
i = i + 1
Loop
'deletes identicals names and sums the value up, puts the values onto sheet("Report_final")
Do While j <= rng
If Cells(j, 3) <> "" Then
mat_name1 = Cells(j, 3).Value
temp_total = Cells(j, 4).Value
For x = j To rng
mat_name2 = Cells(x + 1, 3).Value
If mat_name2 = mat_name1 Then
temp_total = temp_total + Cells(x + 1, 4).Value
Rows(x + 1).ClearContents
End If
Next x
Sheets("Report_Final").Select
Cells(k, 2) = mat_name1
Cells(k, 3) = temp_total
Sheets("Report").Select
Rows(j).ClearContents
k = k + 1
j = j + 1
Else
j = j + 1
End If
Loop
'Labels the new columns in "Report_Final" and calculates the grand total
ws2.Select
Cells(1, 1).Value = wb.Name
Cells(1, 2).Value = "Material"
Cells(1, 3).Value = "Sell Total Price"
Cells(k, 3).Value = Application.Sum(Range(Cells(2, 3), Cells(k, 3)))
Application.DisplayAlerts = False
'Deletes intermediate sheet "Report"
Sheets("Report").Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
In my Main function where I use:
For each wb in Workbooks
PerLineItem
Next wb
It doesn't call PerLineItem for each of the open workbooks but instead trys to perform the function again on the same workbook.
P.S I know there may be a easier way to write all this code but I do not know prior knowledge to VBA :(
Edit : Hi so I've used your code with a little modification and it works fine! But now when i add this next part, it only works through the last workbook, as the counter k does not seem to loop for the earlier workbooks
'~~> cleaning up the sheet still goes here
With wb.Sheets("Report")
rng2 = .Range("B" & .Rows.Count).End(xlUp).Row
MsgBox rng2
Do While j <= rng2
If Cells(j, 3) <> "" Then
mat_name1 = .Cells(j, 3).Value
temp_total = .Cells(j, 4).Value
For x = j To rng2
mat_name2 = .Cells(x + 1, 3).Value
If mat_name2 = mat_name1 Then
temp_total = temp_total + .Cells(x + 1, 4).Value
.Rows(x + 1).ClearContents
End If
Next x
.Rows(j).ClearContents
.Cells(k, 2) = mat_name1
.Cells(k, 3) = temp_total
k = k + 1
j = j + 1
Else
j = j + 1
End If
Loop
MsgBox k
End With
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
P.S I've decided to scrap creating another worksheet and work within "Report"
Try this:
Dim wb As Workbook
For Each wb in Workbooks
If wb.Name <> Thisworkbook.Name Then
PerLineItem wb
End If
Next
Edit1: You need to adapt your sub like this
Private Sub PerLineItem(wb As Workbook)
Dim ws As Worksheet, ws2 As Worksheet
Dim i As Long, j As Long, k As Long, x As Long, rng As Long
Dim temp_total As Double
Dim mat_name1 As string, mat_name2 As String
i = 2: j = 2: k = 2: rng = 0
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
'~~> Improve initializing ws
Set ws = wb.Sheets.Add(wb.Sheets(1))
ws.Name = "Report"
'~~> Directly work on your object; You can also use the commented lines
With ws
.Cells(1, 2) = "WBS" '.Range("B1") = "WBS"
.Cells(1, 3) = "Material" '.Range("C1") = "Material"
.Cells(1, 4) = "Sell Total Price" '.Range("D1") = "Sell Total Price"
End With
'~~> Same with the other worksheet
With wb.Sheets("zero250")
rng = .Range("B" & .Rows.Count).End(xlUp).Row
.AutoFilterMode = False
.Range("B1:B" & rng).AutoFilter 1, "=*RTN*", xlOr, "=*NRT*"
.Range("B1:B" & rng).Offset(1,0).SpecialCells(xlCellTypeVisisble).Copy _
ws.Range("B" & ws.Rows.Count).End(xlup).Offset(1,0)
End With
'~~> cleaning up the sheet still goes here
End Sub
Above code is the equivalent of your code up to generating the Report Sheet only.
Can you continue? :) I run out of time. ;p
Btw, hope this helps.