How do I join the word "Sheet" and an integer to form sheet code name - excel

How can I concatenate the word "Sheet" with a number (say, 2) to form a string that can be used as the code name of a sheet.
I've tried the following piece of code but it doesn't seem to work.
Sh = "Sheet" & 2
Range("A1") = Sh.index

If you want to refer the sheet just based on index you could try something like this as well ... hope it works for you
Sub trial()
i = 2
Sheets(i).Select
End Sub

I assume you want to check if a given â–ºstring argument (CodeNameString) refers to a valid Code(Name) in the VBA project. *)
If so, the following function returns the worksheet to be set to memory; otherwise the second argument IsAvailable passed by reference will change to False and can be used for error checks (c.f. ExampleCall below).
Function SheetByCodename(ByVal CodeNameString As String, ByRef IsAvailable As Boolean) As Object
'check for same CodeName in Sheets collection
Dim ws As Object
For Each ws In ThisWorkbook.Sheets
If ws.CodeName = CodeNameString Then ' check for string identity
Set SheetByCodename = ws ' set sheet object to memory
IsAvailable = True ' assign true to 2nd argument passed ByRef
Exit For
End If
Next
End Function
Example call
Sub ExampleCall()
dim cnt As Long: cnt = 2 ' << change example counter
Dim okay As Boolean ' << needed variable passed as 2nd function argument
With SheetByCodename("Sheet" & cnt, okay)
If okay Then
Debug.Print _
"a) Worksheet Name: " & .Name & vbNewLine & _
"b) Sheet's Code(Name) in Project: " & .CodeName
Else
Debug.Print "Given string refers to no valid Code(Name)."
'do other stuff to handle the wrong input
End If
End With
End Sub
*) Take note of #RonRosenfeld 's important remarks in comment:
"Codename is assigned when the worksheet is created. It can be changed in the properties window. In order to change it programmatically, you need to enable Trust Access to the VBA object model. Otherwise, it's a read-only property. "

Related

Using a collection to check if Names exist

I am trying to create a subroutine that will take a collection of a bunch of strings, step through it, and check for the existence of a named range or formula that has that string as it's name. Trying it with just one item first:
Dim colCritNames As New Collection
colCritNames.Add "Version" 'the name of a named formula
For i = 1 To colCritNames.Count
nm = CStr(colCritNames(i).Name)
nmchk = Check_UW_For_Name(nm)
If Not nmchk Then Call Fail("Critical Name") 'prints a msgbox with the error type so I know what happened
Next i
'...code for if all the names are there...
Function Check_UW_For_Name(find_name As String) As Boolean
Dim wb As Workbook
Set wb = UserFileBook 'global ref to the workbook to check
On Error Goto Fail
Check_UW_For_Name = CBool(Len(wb.Names(find_name).Name) <> 0)
On Error GoTo 0
End Function
Thats edited from the full thing. Check_UW_For_Name was working fine when I just called it with "Version" as the argument Check_UW_For_Name("Version"); it found it in USerFIleBook, and when I called it with "Nope", since there is no Nope name it went to my error handler. But when I try to use a collection to store the names I want to look for I keep getting 'ByRef argument mismatch'. I tried just nm = colCritNames(i) and nm=colCritNames(i).Name, I tried having find_name be Variant and adding a ByVal, and I originally tried having nm be a Name, having Check_UW_For_Name(find_name as Name) and using a for each (for each nm in colCritNames...) and none of it has worked.
How could I set a collection of names and step through it to see if there's a named range/formula that matches in the relevant workbook? Or is there a better way to do this? (I need the collection in other places too)
I don't quite understand what your plan is with a collection, but this will add any cell with the specified string in, as well as any ranges. What you're doing once they've been identified (added to collection) is not clear to me, but hopefully this makes sense and gets you going.
Sub RunForEachString()
Const yourStrings = "foo,bar,hope,this,works"
Dim stringsAsArray() As String
stringsAsArray = Split(yourStrings, ",")
Dim i As Long
For i = LBound(stringsAsArray) To UBound(stringsAsArray)
Call findAllNamesFormulas(stringsAsArray(i), ThisWorkbook)
Next i
End Sub
Private Sub findAllNamesFormulas(theText As String, theWorkbook As Workbook)
Dim ws As Worksheet, n As Name, aCell As Range
Dim aCollection As New Collection
For Each ws In ThisWorkbook.Worksheets
For Each aCell In ws.UsedRange.Cells
If InStr(1, aCell.Formula, theText, vbTextCompare) > 0 Then
aCollection.Add (aCell)
End If
Next aCell
Next ws
For Each n In ThisWorkbook.Names
If InStr(1, n.Name, theText, vbTextCompare) > 0 Then
aCollection.Add (n)
End If
Next n
'not sure what you plan to do after collection?
Debug.Print aCollection.Count
End Sub
This works for me:
Sub Tester()
Dim colCritNames As New Collection, nm, wb As Workbook, msg As String
colCritNames.Add "Version"
colCritNames.Add "NotThere"
colCritNames.Add "AlsoNotThere"
Set wb = ThisWorkbook 'for example
For Each nm In colCritNames
If Not Check_UW_For_Name(wb, CStr(nm)) Then
msg = msg & vbLf & " - " & nm
End If
Next nm
If Len(msg) > 0 Then
MsgBox "One or more required names are missing:" & msg, _
vbExclamation, "Oops"
Exit Sub
End If
'proceed if OK...
End Sub
'check for a defined Name `find_name` in workbook `wb`
' prefer wb as parameter over using a Global....
Function Check_UW_For_Name(wb As Workbook, find_name As String) As Boolean
On Error Resume Next
Check_UW_For_Name = (wb.Names(find_name).Name = find_name)
End Function
You could create a collection of all named ranges in the workbook like this:
Private Sub NamedRangesDemo()
Dim NamedRanges As New Collection, NamedRange As Variant
For Each NamedRange In ThisWorkbook.Names
NamedRanges.Add NamedRange.Name
Next NamedRange
End Sub
And then compare the whatever strings you want to the NamedRanges collection.
By the way, this question is somewhat similar to yours.

Index match in VBA referencing a table

I want to update a line in my table based on a cell in another sheet, and to that end I intend to use the index match function. When I run the code below I get the error that it cannot get the property of the match function class.
What is the correct syntax in this regard?
Sub Update_Customer()
' Declarations
Dim rng as listobject
Dim wf as application.worksheetfunction
Dim cs_sht as string
Set rng = Sheets(1).ListObjects("Table_Customer")
Set ws = ThisWorkbook.ActiveSheet
cs_sht = ws.Name
' ERROR RUNNING THIS LINE vvvvv
wf.Index(rng.ListColumns("Firstname"), wf.Match(cs_sht, rng.ListColumns("Customer ID"), 0)) = ws.Range("C_Firstname").Value
End Sub
Excel functions need to be nested, because a cell's value needs to be parsed as a single step.
VBA code doesn't need to do that. VBA instructions work best and are easier to debug when you split them and make them do as little work as possible.
So instead of this:
wf.Index(rng.ListColumns("Firstname"), wf.Match(cs_sht, rng.ListColumns("Customer ID"), 0))
Split it up:
Dim matchResult As Long
matchResult = WorksheetFunction.Match(cs_sht, rng.ListColumns("Customer ID").DataBodyRange, 0)
Dim indexResult As Variant
indexResult = WorksheetFunction.Index(rng.ListColumns("FirstName").DataBodyRange, matchResult)
Note that you'll get a run-time error if either function fails to find what it's looking for. Use On Error to handle that case:
On Error GoTo CleanFail
Dim matchResult As Long
matchResult = WorksheetFunction.Match(...)
...
Exit Sub
CleanFail:
MsgBox "Could not find record for '" & cs_sht & "'." & vbNewLine & Err.Description
End Sub
Get rid of wf. There's no use to copy object references of objects that are already global. The fewer global variables you use, the better.
if the first name changes I can update the table to match the new name from my worksheet
You can't just assign the indexResult to a new value. The indexResult isn't holding a reference to any cell, it's just the result of the INDEX worksheet function. You need to use that result to get the cell you want to modify:
Dim targetCell As Range
Set targetCell = rng.ListColumns("FirstName").DataBodyRange.Cells(indexResult)
targetCell.Value = ws.Range("C_Firstname").Value

The temp line shows type mismatch error

Sub assign_sr_no()
For Each sh In ThisWorkbook.Sheets
If sh.Name = "master" Then GoTo gogo
If sh.ProtectContents = False Then GoTo gogo
Dim srno As String
Dim temp As Integer
temp = CInt(Right(Sheets("master").Range("A65536").End(xlUp).Text, 4))
srno = "VE17" & "_" & Format(temp + 1, "0000")
sh.Unprotect "VALUE"
sh.Cells.Validation.Delete
sh.Range("L1").Value = srno
sh.Hyperlinks.Add Anchor:=sh.Range("A1"), SubAddress:="master!A1", Address:="", TextToDisplay:="Faridabad Plant"
Call copy_from_ve(srno)
gogo:
Next
End Sub
This piece of code is used to assign serial numbers to the sheet and then there is another code which copies data from the sheet to the master sheet.
The temp line shows
error 13 i.e. type mismatch error.
The whole excel table is empty i have this 25 columns [...]
You can't convert an empty string into an Integer using the CInt function. This code reproduces the error:
Debug.Print CInt("")
You could use the Val function to get yourself a Double that you can convert to an Integer if you're absolutely certain you'll never ever need a value greater than 32,767. Otherwise, use a Long.
temp = Val(Right(Sheets("master").Range("A65536").End(xlUp).Text, 4))
You should really break up that statement though, and pull it out of the loop body if it's constant at every iteration.
Also Excel >2003 has way more than 65K possible rows on a worksheet; consider using .Range("A" & [ActiveSheet.]Rows.Count) (or pull the Sheets("master") into its own With block variable, or better, use the sheet's CodeName:
With MasterSheet ' assuming (Name) property was set to "MasterSheet"
Dim lastValue As String
lastValue = .Range("A" & .Rows.Count).End(xlUp).Text
Dim temp As Long
temp = Val(Right(lastValue, 4)
Dim sh As Worksheet
For Each sh In ThisWorkbook.Worksheets
If sh.Name <> .Name Then ' no need for GoTo jumping here
'...
End If
Next
End With

Generic function procedure working with different workbooks

I am trying to get better coding practice and using generic function.
I am working with several workbooks from a master file.
For example if I want to get the last row I am using the following line of code.
LastRow=Range("A" & Rows.Count).End(xlUp).Row
To retrieve the value with a function I build the function:
-Function 1
Function GetLastRow() As Integer
GetLastRow = Range("A" & Rows.Count).End(xlUp).Row
End Function
Now from my Sub Main() I want to use GetLastRow() for different workbooks or worksheets. I think it is not a good thing to Activate the workbook before calling my function.
Then should I transmit each time the workbook name and worksheet number to my function and change my function to:
-Function 2
Function GetLastRowIn(sWb As String, iWs As Integer) As Integer
GetLastRowIn = Workbooks(sWb).Worksheets(iWs).Range("A" & Rows.Count).End(xlUp).Row
End Function
Or is there a better/simpler way to transmit the workbook and worksheet in which I want to apply the function while keeping it with no argument as in Function 1?
Thanks for your answers!
To make a function more generic you can allow for some flexibility,
but also impose some rulles for the function calls
Generic Function
Option Explicit
Public Function GetLastRow(ByRef ws As Worksheet, Optional ByVal fromCol As Long = 1) As Long
Dim invalidWS As Boolean, lastRow As Long
If Not ws Is Nothing Then 'check 1st param
On Error Resume Next 'check that ws reference is valid (delted WS invalidates ws)
invalidWS = Len(ws.Name) > 0
invalidWS = Err.Number <> 0 'No error if Err.Number = 0
On Error GoTo 0
If Not invalidWS Then
If fromCol > 0 And fromCol <= ws.Columns.Count Then 'validate 2nd param
lastRow = ws.Cells(ws.Rows.Count, fromCol).End(xlUp).Row
'check if WS.fromCol is empty
If lastRow = 1 And Len(ws.Cells(1, fromCol)) = 0 Then lastRow = 0
End If
End If
End If
GetLastRow = lastRow
End Function
Test Sub
Public Sub TestGetLastRow()
'show results in the Immediate Window (VBA Editor: Ctrl+G)
Debug.Print GetLastRow(Sheet1, 1) 'CodeName of WS
Debug.Print GetLastRow(Workbooks(1).Worksheets(1)) 'WS Index
Debug.Print GetLastRow(ActiveWorkbook.Worksheets("Sheet3"), 3) 'WS name (string)
Debug.Print GetLastRow(ThisWorkbook.Worksheets(1), 0) 'invalid column (or -3)
Dim ws As Worksheet
Set ws = Sheet3
Application.DisplayAlerts = False
ws.Delete 'invalidate ws variable
Application.DisplayAlerts = True
Debug.Print GetLastRow(ws, 1) 'function call with invalid ws object
End Sub
Always use Option Explicit to allow the compiler to catch spelling mistakes in variable names
Validate all input
The function call may not include a valid Worksheet, or column number
Allow the Worksheet to be specified by CodeName, WS Index, or WS Name (string)
Allow a default column ID by using Optional for the 2nd parameter
Impose the call to send only a Worksheet object as the first parameter
If you accept strings for it you need to first check that Worksheet("Invalid") exists
Impose the call to request column by Index
If you allow strings in column ID you need to check that the string is between "A" and "XFD"
String length between 1 and 3, and also not allow strings like "XYZ"
This would require a separate function that checks each letter in the string
Strings also create potential for more maintenance if MS decides to increase max columns
Make the function for one purpose (like you did) - don't include other functionality later on
The function should be self contained
Able to detect and handle all possible errors and unexpected input
And generate the most generic and usable output
By returning a 0 in this particular function, calls that expect a valid number will error out for row 0
So you may want to change it to return 1 even if the sheet is empty
and check the top cell if blank after the call returns
As a note:
several workbooks from a master file
A Workbook is a file
A Worksheet is a tab inside a file (a file can contain multiple sheets / tabs)
Always be explicit with all object
Range("A" & Rows.Count).End(xlUp).Row implicitly uses ActiveWorkbook.ActiveSheet
Translates to ActiveWorkbook.ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
If you need to work with several Workbooks and Worksheets, fully qualify your calls
`Workbooks("Book1").Worksheets("Sheet3").Range("A" & Rows.Count).End(xlUp).Row
`Workbooks("Book2").Worksheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Row
so Excel can access the exact object you need to work with
If you use full references your code doesn't depend on active objects
- If a user activates a different Workbook, or Worksheet the code will continue to work without errors
Hope this helps
PS. When using row variables always declare them as Long to be able to handle more than the Integer maximum of 32,767 - currently Excel has a max of 1,048,576 rows (in the future this max can increase even higher)

Excel VBA Macro to copy worksheet and count + 1

I tried to create a macro that is on worksheet overview. This worksheet should be copied and renamed into Test 1. If I go back to the overview worksheet and start the macro again the newname of the ws should be Test 2 and so on.
I already tried Solutions with Count and if then but it is not working. Currently it runs once and then it says the ws already exists:
Sub CopyWs()
Dim NewName As String
ActiveSheet.Copy Before:=ActiveSheet
NewName = "Test " & WsNo + 1
WsNo = WsNo + 1
ActiveSheet.Name = NewName
End Sub
I'm not sure if this is what you're looking for, but I had a similar issue in the past, and I used the following methodology to solve it.
Dim worksheetName As String
worksheetName = "Test 1"
worksheetNumber = 2
CheckName:
For Each ws in Sheets
' If the desired worksheet name exists
If ws.name = worksheetName Then
' Change the worksheetName to be Test x+1 and increment worksheetNumber
worksheetName = "Test " & worksheetNumber
worksheetNumber = worksheetNumber + 1
GoTo CheckName ' return to the CheckName label and start again
End If
Next ws
Essentially, what we are doing is looping through every worksheet in the current workbook, and checking if any of them have the same name as the one we want to insert. If they do, we change the name to a new one and check again; if not, we have our new name.
EDIT: Also, I would suggest putting the above code inside a Function that takes the sheet name as an argument, so you can change the desired name easily, like so:
Function worksheetName(desiredName As String) As String
Dim worksheetName As String
worksheetName = desiredName
' rest of code follows ...
End Function
And then it can be called like so:
NewName = worksheetName("Test")

Resources