I wrote code to reformat a workbook by separating and combining information on separate sheets and then save every sheet separately as a CSV.
The beginning of my code:
Sub All()
Dim Bottom As Long
Dim Header As Long
> 'A. CHECK DATE
If ThisWorkbook.Sheets("ACH PULL").Range("C1") <> Date Then
MsgBox "ERROR" & Chr(10) & "Date on file is different than today's date" & Chr(13) & "Ask client for corrected file"
Exit Sub
Else
> '1. OUTGOING CHECKS
Sheets("OUTGOING CHECKS").Select
Bottom = WorksheetFunction.Match((Cells(Rows.Count, 1).End(xlUp)), Range("A:A"), 0)
Header = WorksheetFunction.Match("Account*", Range("A:A"), 0)
If Bottom <> Header Then
MsgBox "ERROR" & Chr(10) & "The batch contains outgoing checks" & Chr(13) & "Ask client for corrected file"
Exit Sub
Bottom and Header are used to find the header of the range and the last row respectively. I use this so many times in my code on separate sheets.
The code works when I run it from the file that I need to modify. But I need to assign it to a button to another spreadsheet to open the to-be-modified file through VBA and then apply the code. So I added this:
Sub All()
Dim FileToOpen As Variant
Dim NewBatch As Workbook
Dim Bottom As Integer
Dim Header As Integer
FileToOpen = Application.GetOpenFilename(Title:="Find batch file")
If FileToOpen <> False Then
Set NewBatch = Application.Workbooks.Open(FileToOpen)
End If
'A. CHECK DATE
If Sheets("ACH PULL").Range("C1") <> Date Then
MsgBox "ERROR" & Chr(10) & "Date on file is different than today's date" & Chr(13) & "Ask client for corrected file"
Exit Sub
Else
'1. OUTGOING CHECKS
Sheets("OUTGOING CHECKS").Select
Bottom = WorksheetFunction.Match((Cells(Rows.Count, 1).End(xlUp)), Range("A:A"), 0)
Header = WorksheetFunction.Match("Account*", Range("A:A"), 0)
End If
If Bottom <> Header Then
MsgBox "ERROR" & Chr(10) & "The batch contains outgoing checks" & Chr(13) & "Ask client for corrected file"
Exit Sub
' .. The rest of the code
At the line:
Bottom = WorksheetFunction.Match((Cells(Rows.Count, 1).End(xlUp)), Range("A:A"), 0)
I either get 1004 or 400 error.
I have the two pieces (opening a workbook, and reformatting) working separately, but I can't combine them.
I Dim'd the two integers that I need to use before using them.
I tried making multiple changes including NewBatch.Activate.
It didn't made a difference as the opened workbook is already activated. I tried to set the values for Bottom and Header.
Something like this maybe:
Sub All()
Dim FileToOpen As Variant
Dim NewBatch As Workbook
Dim Bottom As Long, Header As Variant 'not Long
FileToOpen = Application.GetOpenFilename(Title:="Find batch file")
If FileToOpen = False Then Exit Sub 'user cancelled open
Set NewBatch = Application.Workbooks.Open(FileToOpen)
'A. CHECK DATE
If NewBatch.Sheets("ACH PULL").Range("C1").Value <> Date Then
ProblemMsg "Date on file is different than today's date." & _
vbLf & "Ask client for corrected file"
Exit Sub
End If
'1. OUTGOING CHECKS
With NewBatch.Sheets("OUTGOING CHECKS")
Bottom = .Cells(.Rows.Count, 1).End(xlUp).Row 'last entry in Col A
Header = Application.Match("Account*", .Range("A:A"), 0) 'not WorksheetFunction.Match
If IsError(Header) Then 'make sure we located "Account*"
ProblemMsg "'Account*' not found in ColA on sheet '" & .Name & "'"
Else
If Bottom <> Header Then
ProblemMsg "The batch contains outgoing checks." & vbLf & _
"Ask client for corrected file."
Exit Sub
End If
End If
End With
'...
'...
End Sub
'Utility sub for displaying error messages
Sub ProblemMsg(msg As String)
MsgBox "ERROR" & vbLf & msg, vbExclamation, "Please review"
End Sub
I have found more reliable performance by defining worksheets and referencing rather than relying on selection or active sheet. Try defining the worksheet this line is being performed on and referencing before the range() and cells() references and see if that helps.
Dim ws as Worksheet
Set ws = Sheets("OUTGOING CHECKS")
Bottom = WorksheetFunction.Match((ws.Cells(Rows.Count, 1).End(xlUp)), ws.Range("A:A"), 0)
Related
For my code, I am trying to find the difference between an objects value from two different days.
Sub GoingBack()
numberCube = InputBox("Which file are we going back to?")
numberYest = numberCube - 1
Workbooks.Open ("C:\Users\user\Downloads\file (" & numberCube & ").xlsx")
Workbooks.Open ("C:\Users\user\Downloads\file (" & numberYest & ").xlsx")
Set Work1 = Workbooks("file (" & numberCube & ").xlsx")
Set Work2 = Workbooks("file (" & numberCube - 1 & ").xlsx")
'Add the Time Difference Column (AA--27)
LastRow67 = Work1.Sheets("67").Cells(Rows.Count, 2).End(xlUp).Row
Work1.Sheets("67").Cells(1, 27).Value = "Time Clock Difference"
Work1.Sheets("67").Cells(1, 27).FormulaR1C1 = "=RC[-15]-VLOOKUP(RC[-21], '[file (" & numberYest & ").xlsx]67'!$F:$L, 7, FALSE)"
Work1.Sheets("67").Range("AA2").Select
Selection.AutoFill Destination:=Range("AA2:AA" & LastRow67)
Work1.Close savechanges:=True
Work2.Close savechanges:=True
End Sub
The line that is throwing the "Application Defined or Object Defined" error is:
Work1.Sheets("67").Cells(1, 27).FormulaR1C1 = "=RC[-15]-VLOOKUP(RC[-21], '[file (" & numberYest & ").xlsx]67'!$F:$L, 7, FALSE)"
I have tried using Range.Formula, and that threw the error as well.
Work1.Sheets("67").Range("AA2").Formula = "=L2-VLOOKUP(F2, '[file (" & numberYest & ").xlsx]67'!$F:$L, 7, FALSE)"
Any help would be appreciated. Thank you so much.
EDIT: I typed in the formula in Excel, and it works. I recorded the inputting of the formula, and the below is the result. I clicked/referenced columns F through L, so I'm not sure why it is only displaying C6:C12 below.
ActiveCell.FormulaR1C1 = "=RC[-15]-VLOOKUP(RC[-21],'[file.xlsx]67'!C6:C12,7,FALSE)"
Just because you've always done something a certain way, doesn't make it good practice. There are a lot of opportunities for improvement here.
Consider this refactor:
Option Explicit ' always!
Sub GoingBack()
' Dim all variables
Dim numberCube As Variant
Dim numberYest As Long
Dim Work1 As Workbook
Dim Work2 As Workbook
Dim LastRow67 As Long
Dim WorkSh1 As Worksheet
Dim WorkSh2 As Worksheet
Dim Pth As String
' avoid repeats of the same data
Pth = "C:\Users\user\Downloads\"
' Might be better to use FileDialog, but anyway...
' Handle user cancel and invalid entry
Do
numberCube = InputBox("Which file are we going back to?")
If numberCube = vbNullString Then
' User canceled, exit
Exit Sub
End If
If IsNumeric(numberCube) Then Exit Do
MsgBox "Enter a Number", vbCritical + vbOKOnly, "Error"
Loop
numberYest = numberCube - 1
' Handle files missing or won't open
On Error Resume Next
Set Work1 = Workbooks.Open(Pth & "file (" & numberCube & ").xlsx")
On Error GoTo 0
If Work1 Is Nothing Then
'Work1 failed to open, what now?
GoTo CleanUp
End If
On Error Resume Next
Set Work2 = Workbooks.Open(Pth & "file (" & numberYest & ").xlsx")
On Error GoTo 0
If Work2 Is Nothing Then
'Work2 failed to open, what now?
GoTo CleanUp
End If
' Set refences to worksheets and handle if missing
On Error Resume Next
Set WorkSh1 = Work1.Sheets("67")
On Error GoTo 0
If WorkSh1 Is Nothing Then
' WorkSh1 doesn't exist, what now?
GoTo CleanUp
End If
On Error Resume Next
Set WorkSh2 = Work2.Sheets("67")
On Error GoTo 0
If WorkSh2 Is Nothing Then
' WorkSh2 doesn't exist, what now?
GoTo CleanUp
End If
'Add the Time Difference Column (AA--27)
' use your references
With WorkSh1
LastRow67 = .Cells(.Rows.Count, 2).End(xlUp).Row
.Cells(1, 27).value = "Time Clock Difference"
' no need for select or Autofill
' Can't use A1 style in FormulaR1C1
.Range(.Cells(1, 27), .Cells(LastRow67, 27)).FormulaR1C1 = _
"=RC[-15]-VLOOKUP(RC[-21], " & WorkSh2.Range("F:L").Address(, , xlR1C1, True) & ", 7, FALSE)"
End With
CleanUp:
Work1.Close SaveChanges:=True
Work2.Close SaveChanges:=False ' you didn't change file 2
End Sub
I have a sub which opens an older version of a checklist I've created, and then imports the data. After the user selects the file, I want to check if a specific sheet and named cell on that sheet exists (for validation they have picked the correct file - the sheet will always be "Main Page" and the cell "Version"). If either doesn't exist, then I want a message box and to exit sub. If they both exist, then continue with the rest of the import.
Most of it works, it's just the first check for the named sheet/cell. The main problem is this bit of the sub:
If Not WorksheetExists("Main Page") Then
MsgBox "The selected file does not appear to be an older version of the checklist." & vbNewLine & vbNewLine & "Please check that you have selected the correct file."
wbCopyFrom.Close SaveChanges:=False
Exit Sub
End If
And the called function:
Function WorksheetExists(sName As String) As Boolean
WorksheetExists = Evaluate("ISREF('" & sName & "'!A1)")
End Function
This function at the moment checks the sheet name fine. But I'm getting a little confused on how to check the cell name - do I need another function or can I just edit the above function to check for both at the same time? ie. I think I can change the line:
WorksheetExists = Evaluate("ISREF('" & sName & "'!A1)")
to include the cell name instead of the A1 bit.
The whole sub and other functions are below for context if that helps.
Sub ImportLists()
If MsgBox("The import process will take some time (approximately 10 minutes); please be patient while it is running. It is recommended you close any other memory-intensive programs before continuing. Click 'Cancel' to run at another time.", vbOKCancel) = vbCancel Then Exit Sub
Application.ScreenUpdating = False
Dim OldFile As Variant, wbCopyFrom As Workbook, wsCopyFrom As Worksheet, wbCopyTo As Workbook, wsCopyTo As Worksheet, OutRng As Range, c As Range, RangeName As Range
Set wbCopyTo = ActiveWorkbook
ChDir ThisWorkbook.Path
OldFile = Application.GetOpenFilename("All Excel Files (*.xls*)," & "*.xls*", 1, "Select a previous version of the checklist", "Import", False)
If TypeName(OldFile) = "Boolean" Then
MsgBox "An error occured while importing the old version." & vbNewLine & vbNewLine & "Please check you have selected the correct checklist file and filetype (.xlsm)."
Exit Sub
End If
Set wbCopyFrom = Workbooks.Open(OldFile)
If Not WorksheetExists("Main Page") Then
MsgBox "The selected file does not appear to be an older version of the checklist." & vbNewLine & vbNewLine & "Please check that you have selected the correct file."
wbCopyFrom.Close SaveChanges:=False
Exit Sub
End If
OldVersion = Right(wbCopyFrom.Sheets("Main Page").Range("Version").Value, Len(wbCopyFrom.Sheets("Main Page").Range("Version").Value) - 1)
NewVersion = Right(wbCopyTo.Sheets("Main Page").Range("Version").Value, Len(wbCopyTo.Sheets("Main Page").Range("Version").Value) - 1)
If NewVersion < OldVersion Then
MsgBox "The selected older version of the checklist (v" & OldVersion & ") appears to be newer than the current version (v" & NewVersion & ")." & vbNewLine & vbNewLine & "Please check that you have selected the correct older version of the checklist or that the current checklist is not an older version."
wbCopyFrom.Close SaveChanges:=False
Exit Sub
End If
For Each wsCopyFrom In wbCopyFrom.Worksheets
If wsCopyFrom.Name <> "Set List" And wsCopyFrom.Name <> "Rarity Type Species List" And wsCopyFrom.Name <> "Need List" And wsCopyFrom.Name <> "Swap List" And wsCopyFrom.Name <> "Reference List" Then
Set wsCopyTo = wbCopyTo.Worksheets(wsCopyFrom.Name)
Set OutRng = UsedRangeUnlocked(wsCopyFrom)
If Not OutRng Is Nothing Then
For Each c In OutRng
If wsCopyTo.Range(c.Address).Locked = False Then
c.Copy wsCopyTo.Range(c.Address)
End If
Next c
End If
End If
Next wsCopyFrom
wbCopyFrom.Close SaveChanges:=False
Call CalcRefilter
Application.ScreenUpdating = True
MsgBox "The checklist was successfully imported from version " & OldVersion & " and updated to version " & NewVersion & "." & vbNewLine & vbNewLine & "Don't forget to save the new version."
End Sub
Function WorksheetExists(sName As String) As Boolean
WorksheetExists = Evaluate("ISREF('" & sName & "'!A1)")
End Function
Function UsedRangeUnlocked(ws As Worksheet) As Range
Dim RngUL As Range, c As Range
For Each c In ws.UsedRange.Cells
If Not c.Locked Then
If RngUL Is Nothing Then
Set RngUL = c
Else
Set RngUL = Application.Union(RngUL, c)
End If
End If
Next c
Set UsedRangeUnlocked = RngUL
End Function
You can try to access the range. If it throws an error it does not exist:
Function RangeExists(RangeName As String) As Boolean
Dim rng As Range
On Error Resume Next
Set rng = Range(RangeName)
On Error GoTo 0 'needed to clear the error. Alternative Err.Clear
RangeExists = Not rng Is Nothing
End Function
Or to check at once if both exists (worksheet and range):
Function SheetAndRangeExists(WorksheetName As String, RangeName As String) As Boolean
Dim rng As Range
On Error Resume Next
Set rng = Worksheets(WorksheetName).Range(RangeName)
On Error GoTo 0
SheetAndRangeExists = Not rng Is Nothing
End Function
If you want to test it in a specific workbook:
Function SheetAndRangeExists(InWorkbook As Workbook, WorksheetName As String, RangeName As String) As Boolean
Dim rng As Range
On Error Resume Next
Set rng = InWorkbook.Worksheets(WorksheetName).Range(RangeName)
On Error GoTo 0
SheetAndRangeExists = Not rng Is Nothing
End Function
and call like SheetAndRangeExists(ThisWorkbook, "Main Page", "Version")
I am very new to VBA and coding in general. I am struggling with this bit of code where I would like to copy the data in row A in sheet "System 1" and use it in my validation list. However, with this current bit of code, it seems that I am getting the row data from my current sheet and not from sheet "System 1"
What am I doing wrong here? What's the best practice when referring to other sheets to optimise the speed sheet of excel?
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim range1 As Range, rng As Range
Set Sheet = Sheets("System 1")
Set range1 = Sheets("System 1").Range("A1:BB1")
Set rng = Range("M2")
With rng.Validation
.Delete 'delete previous validation
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:="='" & Name & "'!" & .range1.Address
End With
This code should give you a good start. Fix and adjust to your needs. Study the customize sections of the code carefully. The WSChange should work perfectly except maybe there is something weird about those public variables (you can always put them into the procedure ... and the events are ... I don't get them, but I will soon enough.
You cannot use a range from a different worksheet to use it as a validation range (similar to conditional formatting, that is for Excel 2003), so you have to define a name to use as a range.
This one goes into a module. I just couldn't see it in the worksheet:
Option Explicit
Public strMain As String
Public Const cStrValList As String = "ValList" 'Validation List Name
Sub WSChange()
'-- Customize BEGIN --------------------
'Name of the main worksheet containing the validation RANGE.
'*** The worksheet should be defined by name so that this script can be run ***
'*** from other worksheets (Do NOT use the Activesheet, if not necessary). *** ***
Const cStrMain As String = "Main" 'If "" then Activesheet is used.
'Name of the worksheet containing the validation LIST.
Const cStrSys As String = "System 1"
'*** The next two constants should be defined as first cell ranges, so when ***
'*** adding new data, the last cell could be calculated again and the data *** ***
'*** wouldn't be 'out of bounds' (outside the range(s)).
'Validation RANGE Address. Can be range or first cell range address.
Const cStrMainRng As String = "$M$2" 'orig. "$M$2"
'Validation LIST Range Address. Can be range or first cell range address.
Const cStrSysRng As String = "$A$1" 'orig. "$A$1:$BB$1"
'-- Customize END ----------------------
strMain = cStrMain
Dim oWsMain As Worksheet
Dim oRngMain As Range
Dim oWsSys As Worksheet
Dim oRngSys As Range
Dim oName As Name
Dim strMainRng As String
Dim strMainLast As String
Dim strSysRng As String
Dim strSysLast As String
'---------------------------------------
On Error GoTo ErrorHandler 'No error handling so far!
'---------------------------------------
'Main Worksheet
If cStrMain <> "" Then 'When cStrMain is used as the worksheet name.
Set oWsMain = ThisWorkbook.Worksheets(cStrMain)
Else 'cStrMain = "", When ActiveSheet is used instead. Not recommended.
Set oWsMain = ThisWorkbook.ActiveSheet
End If
With oWsMain
If .Range(cStrMainRng).Cells.Count <> 1 Then
strMainRng = cStrMainRng
Else
'Calculate Validation Range Last Cell Address
strMainLast = .Range(Cells(Rows.Count, _
.Range(cStrMainRng).Column).Address).End(xlUp).Address
'Calculate Validation Range and assign to a range variable
strMainRng = cStrMainRng & ":" & strMainLast 'First:Last
End If
Set oRngMain = .Range(strMainRng) 'Validation Range
End With
'---------------------------------------
'System Worksheet
Set oWsSys = Worksheets(cStrSys) 'Worksheet with Validation List
With oWsSys
If .Range(cStrSysRng).Cells.Count <> 1 Then
strSysRng = cStrSysRng
Else
'Calculate Validation Range Last Cell Address
strSysLast = .Range(Cells(.Range(cStrSysRng).Row, _
Columns.Count).Address).End(xlToLeft).Address
'Calculate Validation Range and assign to a range variable
strSysRng = cStrSysRng & ":" & strSysLast 'First:Last
End If
Set oRngSys = .Range(strSysRng) 'Validation List Range
End With
'---------------------------------------
'Name
For Each oName In ThisWorkbook.Names
If oName.Name = cStrValList Then
oName.Delete
Exit For 'If found, Immediately leave the For Each Next loop.
End If
Next
ThisWorkbook.Names.Add Name:=cStrValList, RefersTo:="='" & cStrSys _
& "'!" & strSysRng
With oRngMain.Validation
.Delete 'delete previous validation
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:="=" & cStrValList
End With
'---------------------------------------
ProcedureExit:
Set oRngMain = Nothing
Set oRngSys = Nothing
Set oWsSys = Nothing
Set oWsMain = Nothing
Exit Sub
'---------------------------------------
ErrorHandler:
'Handle Errors!
MsgBox "An error has occurred.", vbInformation
GoTo ProcedureExit
'---------------------------------------
End Sub
And some 'eventing', not so good, but I've run out of patience.
This actually goes into the 'System 1' worksheet. You should maybe figure out something like that for the 'main' sheet.
Option Explicit
Public PreviousTarget As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
'MsgBox Target.Cells.Count
'-- Customize BEGIN --------------------
Const cStr1 = "Validation List Change"
Const cStr2 = "Values have changed"
Const cStr3 = "Previous Value"
Const cStr4 = "Current Value"
'-- Customize END ----------------------
Dim str1 As String
'Values in the NAMED RANGE (cStrValList)
'Only if a cell in the named range has been 'addressed' i.e. a cell is
'selected and you start typing or you click in the fomula bar, and then
'enter is pressed, this will run which still doesn't mean the value has
'been changed i.e. the same value has been written again... If the escape
'key is used it doesn't run.
If Not Intersect(Target, Range(cStrValList)) Is Nothing Then
If Target.Cells.Count > 1 Then
WSChange
MsgBox "Cannot handle multiple cells, yet."
Else
'Check if the value has changed.
If PreviousTarget <> Target.Value Then 'The value has changed.
WSChange
str1 = cStr1 & vbCrLf & vbCrLf & cStr2 & ":" & vbCrLf & vbCrLf & "'" & _
Target.Address & "' " & cStr3 & " = '"
str1 = str1 & PreviousTarget & "'" & vbCrLf & "'" & Target.Address
str1 = str1 & "' " & cStr4 & " = '" & Target.Value & "'."
MsgBox str1, vbInformation
Else 'The value has not changed.
End If
End If
Else 'The cell range is out of bounds.
End If
'Values in the NAMED RANGE ROW outside the NAMED RANGE (cStrValList9
Dim strOutside As String
'Here comes some bad coding.
strOutside = Range(cStrValList).Address
strOutside = Split(strOutside, ":")(1)
strOutside = Range(strOutside).Offset(0, 1).Address
strOutside = strOutside & ":" _
& Cells(Range(strOutside).Row, Columns.Count).Address
If Not Intersect(Target, Range(strOutside)) Is Nothing Then
If Target.Cells.Count > 1 Then
WSChange
MsgBox "Cannot handle multiple cells, yet."
Else
If PreviousTarget <> Target.Value Then 'The value has changed.
If strMain <> "" Then
WSChange
Else
MsgBox "You have to define a worksheet by name under 'cStrMain'."
End If
End If
End If
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'This gets the 'previous' Target value. This is gold concerning the speed of
'execution. It's a MUST REMEMBER.
PreviousTarget = Target.Value
End Sub
Sub vallister()
MsgBox Range(cStrValList).Address
End Sub
Sub sdaf()
End Sub
Edit:
After doing a bit more research I stumbled on this handy little shortcut:
Just right click on the little arrows on the bottom left corner to show all sheets - no code required!
I have an excel workbook with 100 tabs. Luckily for me the tabs are all numbered 1-100. I Have an index page with all the numbers in a row and I would like to make a row next to that row with a hyperlink to the numbered tab.
A B
---------------------------
| 1 | link to tab 1 |
---------------------------
| 2 | link to tab 2 |
---------------------------
etc...
So far the most promising thing I've found is:
=Hyperlink(“C:\Documents and Settings\Admin1\Desktop\” & A1 & “.xls”,A1)
I know that the hyperlink function expects:
=HYPERLINK(link_location,friendly_name)
And when I do it manually, I get this:
=HYPERLINK('1'!$A$1,A1)
So I want to do something like this:
=HYPERLINK('& A1 &'!$A$1,A1)
But it's not working. Any help is much appreciated. Also, if there is an easier way to approach this - I am all ears.
With code something like this
Press Alt + F11 to open the Visual Basic Editor (VBE).
From the Menu, choose Insert-Module.
Paste the code into the right-hand code window.
Close the VBE, save the file if desired.
In excel-2003 go to Tools-Macro-Macros and double-click CreateTOC
In excel-2007 click the Macros button in the Code group of the Developer tab, then click CreateTOC in the list box.
Option Explicit
Sub CreateTOC()
Dim ws As Worksheet
Dim nmToc As Name
Dim rng1 As Range
Dim lngProceed As Boolean
Dim bNonWkSht As Boolean
Dim lngSht As Long
Dim lngShtNum As Long
Dim strWScode As String
Dim vbCodeMod
'Test for an ActiveWorkbook to summarise
If ActiveWorkbook Is Nothing Then
MsgBox "You must have a workbook open first!", vbInformation, "No Open Book"
Exit Sub
End If
'Turn off updates, alerts and events
With Application
.ScreenUpdating = False
.DisplayAlerts = False
.EnableEvents = False
End With
'If the Table of Contents exists (using a marker range name "TOC_Index") prompt the user whether to proceed
On Error Resume Next
Set nmToc = ActiveWorkbook.Names("TOC_Index")
If Not nmToc Is Nothing Then
lngProceed = MsgBox("Index exists!" & vbCrLf & "Do you want to overwrite it?", vbYesNo + vbCritical, "Warning")
If lngProceed = vbYes Then
Exit Sub
Else
ActiveWorkbook.Sheets(Range("TOC_Index").Parent.Name).Delete
End If
End If
Set ws = ActiveWorkbook.Sheets.Add
ws.Move before:=Sheets(1)
'Add the marker range name
ActiveWorkbook.Names.Add "TOC_INDEX", ws.[a1]
ws.Name = "TOC_Index"
On Error GoTo 0
On Error GoTo ErrHandler
For lngSht = 2 To ActiveWorkbook.Sheets.Count
'set to start at A6 of TOC sheet
'Test sheets to determine whether they are normal worksheets
ws.Cells(lngSht + 4, 2).Value = TypeName(ActiveWorkbook.Sheets(lngSht))
If TypeName(ActiveWorkbook.Sheets(lngSht)) = "Worksheet" Then
'Add hyperlinks to normal worksheets
ws.Hyperlinks.Add Anchor:=ws.Cells(lngSht + 4, 1), Address:="", SubAddress:="'" & ActiveWorkbook.Sheets(lngSht).Name & "'!A1", TextToDisplay:=ActiveWorkbook.Sheets(lngSht).Name
Else
'Add name of any non-worksheets
ws.Cells(lngSht + 4, 1).Value = ActiveWorkbook.Sheets(lngSht).Name
'Colour these sheets yellow
ws.Cells(lngSht + 4, 1).Interior.Color = vbYellow
ws.Cells(lngSht + 4, 2).Font.Italic = True
bNonWkSht = True
End If
Next lngSht
'Add headers and formatting
With ws
With .[a1:a4]
.Value = Application.Transpose(Array(ActiveWorkbook.Name, "", Format(Now(), "dd-mmm-yy hh:mm"), ActiveWorkbook.Sheets.Count - 1 & " sheets"))
.Font.Size = 14
.Cells(1).Font.Bold = True
End With
With .[a6].Resize(lngSht - 1, 1)
.Font.Bold = True
.Font.ColorIndex = 41
.Resize(1, 2).EntireColumn.HorizontalAlignment = xlLeft
.Columns("A:B").EntireColumn.AutoFit
End With
End With
'Add warnings and macro code if there are non WorkSheet types present
If bNonWkSht Then
With ws.[A5]
.Value = "This workbook contains at least one Chart or Dialog Sheet. These sheets will only be activated if macros are enabled (NB: Please doubleclick yellow sheet names to select them)"
.Font.ColorIndex = 3
.Font.Italic = True
End With
strWScode = "Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)" & vbCrLf _
& " Dim rng1 As Range" & vbCrLf _
& " Set rng1 = Intersect(Target, Range([a6], Cells(Rows.Count, 1).End(xlUp)))" & vbCrLf _
& " If rng1 Is Nothing Then Exit Sub" & vbCrLf _
& " On Error Resume Next" & vbCrLf _
& " If Target.Cells(1).Offset(0, 1) <> ""Worksheet"" Then Sheets(Target.Value).Activate" & vbCrLf _
& " If Err.Number <> 0 Then MsgBox ""Could not select sheet"" & Target.Value" & vbCrLf _
& "End Sub" & vbCrLf
Set vbCodeMod = ActiveWorkbook.VBProject.VBComponents(ws.CodeName)
vbCodeMod.CodeModule.AddFromString strWScode
End If
'tidy up Application settins
With Application
.ScreenUpdating = True
.DisplayAlerts = True
.EnableEvents = True
End With
ErrHandler:
If Err.Number <> 0 Then MsgBox Err.Description & vbCrLf & "Please note that your Application settings have been reset", vbCritical, "Code Error!"
End Sub
My snippet:
Sub AddLinks()
Dim wksLinks As Worksheet
Dim wks As Worksheet
Dim row As Integer
Set wksLinks = Worksheets("Links")
wksLinks.UsedRange.Delete
row = 1
For Each wks In Worksheets
' Debug.Print wks.Name
wks.Hyperlinks.Add wksLinks.Cells(row, 1), "", wks.Name & "!A1", , wks.Name
row = row + 1
Next wks
End Sub
Assumes a worksheet named 'Links"
Might not be a direct answer to your method, but I would create something more pleasing to the eye, like ... some shapes formatted nicely and then asign some basic macros to them, for selecting the sheets.
This can be easely modified to go to a specific address (like the Go TO Ctrl+Gbuilt in Excel feature).Hope this helps on the fashion style of your file :)
EDIT!
Don't know why my answer received a -1 rating. As I've said it's an alternative and not a direct solution to the given question. Still, I do believe my initial answer was superficial without a proven/working VBA code, thus I've developed a little practical example below:
Sub Add_Link_Buttons()
'Clear any Shapes present in the "Links" sheet
For j = ActiveSheet.Shapes().Count To 1 Step -1
ActiveSheet.Shapes(j).Delete
Next j
'Add the shapes and then asign the "Link" Macros
For i = 1 To ActiveWorkbook.Sheets.Count
ActiveSheet.Shapes.AddShape Type:=msoShapeRoundedRectangle, Left:=50, Top:=i * 25, Width:=100, Height:=25
ActiveSheet.Shapes(i).OnAction = "Select_Sheet" & i
'even add the the sheet Name as Test:
ActiveSheet.Shapes(i).TextFrame2.TextRange.Characters.Text = Sheets(i).Name
Next i
End Sub
where the "basic Select Macros" whould be:
Sub Select_Sheet1()
ActiveWorkbook.Sheets(1).Select
End Sub
Sub Select_Sheet2()
ActiveWorkbook.Sheets(2).Select
End Sub
Sub Select_Sheet3()
ActiveWorkbook.Sheets(3).Select
End Sub
' and so on!
' Note! to link a specific address within the sheets use the range like in 'Sheets(1).Range("A1").Select
Again, This is an alternative and doesn't add hyperlinks (as asked), but enables the sheet select from the same location.
TO address the buttons to links for outside files, simply define the address > filename/workbook Sheets() and Open ;)
Here is the code I use:
Sub CreateIndex()
'This macro checks for an Index tab in the active worksheet and creates one if one does not already exist.
'If an Index tab already exists, the user is asked to continue. If they continue, the original Index tab is replaced by a new Index tab. If they do not continue, the macro stops.
'The user is then asked if they want to create a link back to the Index tab on all other worksheets (yes or no) and the macro acts accordingly.
Dim wsIndex As Worksheet
Dim wSheet As Worksheet
Dim retV As Integer
Dim i As Integer
With Application
.DisplayAlerts = False
.ScreenUpdating = False
End With
Set wsIndex = Worksheets.Add(Before:=Sheets(1))
With wsIndex
On Error Resume Next
.Name = "Index"
If Err.Number = 1004 Then
If MsgBox(Prompt:="A sheet named ""Index"" already exists. Do you wish to continue by replacing it with a new Index?", _
Buttons:=vbInformation + vbYesNo) = vbNo Then
.Delete
MsgBox "No changes were made."
GoTo EarlyExit:
End If
Sheets("Index").Delete
.Name = "Index"
End If
On Error GoTo 0
retV = MsgBox("Create links back to ""Index"" sheet on other sheets?", vbYesNo, "Linking Options")
For Each wSheet In ActiveWorkbook.Worksheets
If wSheet.Name <> "Index" Then
i = i + 1
If wSheet.Visible = xlSheetVisible Then
.Range("B" & i).Value = "Visible"
ElseIf wSheet.Visible = xlSheetHidden Then
.Range("B" & i).Value = "Hidden"
Else
.Range("B" & i).Value = "Very Hidden"
End If
.Hyperlinks.Add Anchor:=.Range("A" & i), Address:="", SubAddress:="'" & wSheet.Name & "'!A1", TextToDisplay:=wSheet.Name
If retV = 6 And wSheet.Range("A1").Value <> "Index" Then
wSheet.Rows(1).Insert
wSheet.Range("A1").Hyperlinks.Add Anchor:=wSheet.Range("A1"), Address:="", SubAddress:="'" & .Name & "'!A1", TextToDisplay:=.Name
End If
End If
Next wSheet
.Rows(1).Insert
With .Rows(1).Font
.Bold = True
.Underline = xlUnderlineStyleSingle
End With
.Range("A1") = "Sheet Name"
.Range("B1") = "Status"
.UsedRange.AutoFilter
Rows("2:2").Select
ActiveWindow.FreezePanes = True
Application.Goto Reference:="R1C1"
.Columns("A:B").AutoFit
End With
With ActiveWorkbook.Sheets("Index").Tab
.Color = 255
.TintAndShade = 0
End With
EarlyExit:
With Application
.DisplayAlerts = True
.ScreenUpdating = True
End With
End Sub
-Mike
I am trying to reassign all the linked cells for checkboxes on three given worksheets in a large collection of workbooks.
The macro I have works successfully on any book I have open:
Sub CheckBoxesControl()
On Error Resume Next
Dim i As Long
For i = 1 To 400
Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
Sheet21.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AB" & i
Sheet22.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AC" & i
Next i
End Sub
However I want to run this across a large number of sheets, so I tried the following:
Sub CheckBoxesControl()
On Error Resume Next
Dim path As String
Dim file As String
Dim wkbk As Workbook
Dim i As Long
Application.ScreenUpdating = False
Application.DisplayAlerts = False
path = "C:\file\path\"
file = Dir(path)
Do While Not file = ""
Workbooks.Open (path & file)
Set wkbk = ActiveWorkbook
For i = 1 To 400
Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
Sheet21.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AB" & i
Sheet22.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AC" & i
Next i
wkbk.Save
wkbk.Close
file = Dir
Loop
End Sub
The macro certainly opens and closes each file, and runs without error, but it is not having the desired affect.
It only changes the check boxes for the sheet I run the macro from still (despite apparently opening saving and closing all the others).
Am I failing to correctly set the active workbook?
EDIT 1: Suggested fix (failed)
Method suggested in comments (proved unsuccessful):
Sub CheckBoxesControl()
On Error Resume Next
Dim path As String
Dim file As String
Dim wkbk As Workbook
Dim i As Long
Application.ScreenUpdating = False
Application.DisplayAlerts = False
path = "C:\file\path\"
file = Dir(path)
Do While Not file = ""
Set wkbk = Workbooks.Open(path & file)
For i = 1 To 400
wkbk.Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
wkbk.Sheet21.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AB" & i
wkbk.Sheet22.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AC" & i
If Err.Number <> 0 Then
End If
Next i
wkbk.Save
wkbk.Close
file = Dir
Loop
End Sub
EDIT 2: REMOVING ON ERROR RESUME NEXT
Suggestedion to remove the error ignoring has illustrated the following: when the macro runs an error:
Run-time error 1004
The item with the specific name wasn't found.
Debugging this error highlights:
Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
I believe I realise what this issue is: I'm using a "go between 1 and 400" loop to ensure I catch all the checkboxes on each page, but there isn't a checkbox for each one of those instances, (checkbox1 doesn't exist for example, on all pages - notably not on sheet 4)
I remember now this is why I had On error resume next there in the first place... but I need "next" to be the next "i" in the loop, not the next expression completely.
Update 4
For those keeping score at home, the problem is that OP was using the sheets CodeName, which cannot be used when referring to it from a macro in another spreadsheet.
Modify to accept the worksheet Name, and either of the subs can be called like:
Dim ws As Worksheet
Set ws = wkbk.Sheets("10. Prevention Finance")
UpdateChkBoxes3 ws, "ChkBoxOutput!AA"
Set ws = wkbk.Sheets("...") '#Modify the sheet name
UpdateChkBoxes3 ws, "ChkBoxOutput!AB"
Set ws = wkbk.Sheets("...") '#Modify the sheet name
UpdateChkBoxes3 ws, "ChkBoxOutput!AC"
Update 3 (non-ActiveX Checkboxes)
Sub UpdateChkBoxes3(sht as Worksheet, lnkdCell as String)
Dim cb as CheckBox
Dim cbNum As Integer
With sht
For Each cb In sht.CheckBoxes
cbNum = Replace(cb.Name, "Check Box ", vbNullString)
cb.LinkedCell = lnkdCell & cbNum
Next
End With
I also revised the sub in Update 2, previously had pasted in my testing code, instead of the proper sub that requires sht/lnkdCell as arguments.
Update 2
To account for non-indexed checkbox names, but still looping over all checkboxes in each worksheet, call this subroutine. I attempt to get the numeric value from the checkbox's .Name property, this should relate it to the cell location just like your i indexing did before, only you will avoid errors where checkboxes don't exist, because we're not looping over an Index, we're looping over the shapes themselves. This should work with ActiveX checkboxes:
Sub UpdateChkBoxes2(sht As Worksheet, lnkdCell As String)
'To address non-sequential/missing check box names not aligned with index
Dim cb As OLEObject
Dim cbNum As Integer
With sht
For Each cb In sht.OLEObjects
If cb.progID Like "Forms.CheckBox*" Then
cbNum = Replace(cb.Name, "Check Box ", vbNullString)
cb.LinkedCell = lnkdCell & cbNum
End If
Next
End With
End Sub
Update
Try something like this, which assumes CheckBoxes are named sequentially according to their index, and that there are no missing indices.
UpdateChkBoxes Sheet4, "ChkBoxOutput!AA"
UpdateChkBoxes Sheet21, "ChkBoxOutput!AB"
UpdateChkBoxes Sheet22, "ChkBoxOutput!AC"
'## Replaced the following error-prone code:
'For i = 1 To .CheckBoxes.Count
' wkbk.Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
' wkbk.Sheet21.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AB" & i
' wkbk.Sheet22.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AC" & i
' If Err.Number <> 0 Then
'
' End If
'Next i
Then, include this subroutine:
Sub UpdateChkBoxes(sht as Worksheet, lnkdCell as String)
With sht
For i = 1 to .CheckBoxes.Count
.CheckBoxes("Check Box " & i).LinkedCell = lnkdCell & i
Next
End With
End Sub
Original Response
OK, I think the problem is that nothing in your code is actually iterating over the files within a folder. You will need to use a FileSystemObject to do this. You can enable reference to the Microsoft Scripting Runtime dictionary, or, simply declare these variables as generic Object instead of Scripting....
Create an FSO, then assign a folder, and loop over the File objects within this folder. Open the file, and then pass it to a subroutine to perform your checkbox operations.
Something like this:
Option Explicit
Sub LoopFiles()
'## Requires reference to Microsoft Scripting Runtime Library
Dim path As String
Dim fso As New Scripting.FileSystemObject
Dim folder As Scripting.folder
Dim file As Scripting.file
Dim wkbk As Workbook
path = ThisWorkbook.path
Set folder = fso.GetFolder(path)
For Each file In folder.Files
Select Case UCase(Right(file.Name, 4)) '## Make sure you're only working on XLS file types
Case "XLSX", "XLSM", ".XLS" 'etc.
'
Set wkbk = Workbooks.Open(file.Name)
'Now, send this WOrkbook Object to a subroutine
CheckBoxesControl wkbk
wkbk.Save
wkbk.Close
Case Else
'Do nothing
End Select
Next
Set folder = Nothing
Set fso = Nothing
End Sub
Sub CheckBoxesControl(wkbk As Workbook)
Dim i As Long
On Error Resume Next
With wkbk
For i = 1 To 400
.Sheet4.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AA" & i
.Sheet21.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AB" & i
.Sheet22.CheckBoxes("Check Box " & i).LinkedCell = "ChkBoxOutput!AC" & i
Next i
End With
On Error GoTo 0
End Sub