Sub works from "Book1" but not from "PERSONAL.XLSB - vba

I use Excel 2016 and this Sub is copied from: VBA to remove numbers from start of string/cell
Sub RemoveNonDigits()
Dim X As Long, Z As Long, LastRow As Long, CellVal As String
Const StartRow As Long = 1
Const DataColumn As String = "A"
Application.ScreenUpdating = False
LastRow = Cells(Rows.Count, DataColumn).End(xlUp).Row
For X = StartRow To LastRow
CellVal = Cells(X, DataColumn)
While IsNumeric(Left(CellVal, 1)) ' Here
CellVal = Mid(CellVal, 2) ' all digits at the start
Wend ' are removed
Cells(X, DataColumn) = trim(CellVal)
Next
Application.ScreenUpdating = True
End Sub
When I copy this sub from your site and paste it into a new module, in a new workbook (Book1), it works fine.
When I copy the sub from Book1 and paste it into a new module in PERSONAL.XLSB, I get this error:
"Compile error:
Wrong number of arguments or invalid property assignment".
"Sub RemoveNonDigits()" is highlighted in yellow and "trim" is highlighted in blue.
Can anyone tell me why the Sub works from "Book1" but not from "PERSONAL.XLSB"?
Any help is much appreciated.
Thank you in advance.

It seems that you had another function somewhere called trim which took other parameters, or may be a property or a variable in some other module, referenced library or add-in.
The call VBA.Trim solved your problem by disambiguating the function's name. By prefixing the function's name with VBA., you tell the compiler explicitly that you want to invoke the function Trim that is built-in with VBA, not any other one.
Prefixing with VBA. is not usually necessary, but sometimes it helps disambiguate name resolution.

Related

vba: variable not defined in activeCell

I'm new to vba so am learning. I'm struggling to understand why this is failing in Excel 2016 and my research is drawing a blank. I have the following code
Option Explicit
Sub Range_End_Method()
Dim lRow As Long
Dim lCol As Long
Dim i As Integer
Dim rng As Range
Set rng = ActiveCell
lRow = Cells(Rows.Count, 1).End(xlUp).Row
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
Do While rng.Value <> Empty
If InStr(rng.Value, "Petrol") = 0 Then
currentRow = ActiveCell.Row
Worksheets("MySheet").Cells(currentRow, 5) = "Shopping"
Set rng = rng.Offset(1)
rng.Select
Else
currentRow = ActiveCell.Row
Worksheets("MySheet").Cells(currentRow, 9) = "Not Found"
Set rng = rng.Offset(1)
rng.Select
End If
Loop
End Sub
If I open the vba editor and type this into a "Microsoft Excel Objects" sheet I don't get an error. But if I run as a "Modules" naming my module as "m_End" I get an error thrown up saying
Compile Error
Variable not defined
The variable it highlights in vba editor is "currentRow" from the line in the first "If condition":
currentRow = ActiveCell.Row
I don't understand what the difference is and how to fix the error.
Any help would be appreciated.
Since you posted the rest of your code, that error is clearly because you did not declare currentRow in your Dim statements. Possibly option Explicit was not in your sheet module, so no error.
In any event, go to Tools ► Options ► Editor ► Code Settings and Select Require Variable Declaration It will save you loads of debugging time in the future. Perhaps in your sheet module you did not have Option Explicit
I would declare it as a Long
Oh, and in VBA there is no advantage (and there are some disadvantages) to declaring a data type as Integer. They get converted to Longs internally anyway; and the values which can be assigned are limited. Check VBA specifications for data types for the various limits.

Selecting first empty row with specific columns for each sheet VBA

Is there any way that I can possibly make the function change to a specific column for each sheet in the ActiveWorkbookI tried various versions but can't seem to get it right.
Sub resetFilters()
Dim sht As Worksheet
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
'On Error Resume Next
If ActiveSheet.FilterMode Then
ActiveSheet.ShowAllData
End If
Range("A3:T3").ClearContents
Application.ScreenUpdating = True
Call GetLastRow
Exit Sub
ErrorHandler:
Debug.Print "Error number: " & Err.Number & " " & Err.Description
End Sub
Private Function SelectFirstEmptyRowInColumnH(ByVal sheet As Worksheet, Optional ByVal fromColumn As Long = 8) As Long
SelectFirstEmptyRowInColumnH = sheet.Cells(sheet.Rows.Count, fromColumn).End(xlUp).Row
End Function
Private Sub GetLastRow()
Dim selectLastRow As Long
selectLastRow = SelectFirstEmptyRowInColumnH(ActiveSheet, 8)
Cells(selectLastRow, 8).Offset(1, 0).Select
End Sub
A worksheet is an object and can't be passed as an argument ByVal. It must be ByRef, which is the default and can therefore be omitted. Note also that Sheet is a word reserved for VBA's use. In most cases VBA will be able to determine your intention and allow you to use its vocabulary the way you wish, but for you, when faced with the task of trouble shooting, it is a hell of a job to determine in each case whether Sheet means VBA's sheet or your own sheet. Select any word in your code and press F1 to let VBA show you the meaning it attaches to it and how to use it.
Other than that, note that your function returns the last used row. The first empty one is the next one after that. So, I would write that function somewhat like this:-
Private Function FirstEmptyRow(Ws As Worksheet, _
Optional ByVal Clm As Long = 1) As Long
With Ws
FirstEmptyRow = .Cells(.Rows.Count, Clm).End(xlUp).Row + 1
End With
End Function
Observe that I changed the default for the optional column to 1. The default should be both the most logical choice and the one most commonly used. In the case of the last row that is the first column, column A.
Here is an alternative based on your comment (which I couldn't fully understand). This code looks for the word "Style" in Rows(3) of the ActiveSheet and returns the next blank row in the column where "Style" was found.
Private Function FirstEmptyRow() As Long
' 9 Apr 2017
Dim Clm As Long
With ActiveSheet
On Error GoTo ErrHandler:
Clm = WorksheetFunction.Match("Style", .Rows(3), 0)
FirstEmptyRow = .Cells(.Rows.Count, Clm).End(xlUp).Row + 1
End With
ErrHandler:
Err.Clear
End Function
If the word "Style" isn't found an error will occur and the execution will jump to the Label ErrHandler: which does nothing. You might want to let it handle the situation in some way. As the function stands the row number it returns will be zero which will cause an error if you try to address that row.
you could use this function:
Private Function SelectFirstEmptyRowInColumnWithGivenHeader(ByVal sheet As Worksheet, Optional ByVal header As String = "Style") As Long
Dim col As Variant
With sheet
col = Application.Match(header, .Rows(1), 0)
If Not IsError(col) Then
.Activate '<--| you must select a sheet to activate a cell of it
.Cells(.Rows.Count, col).End(xlUp).Offset(1).Select
End If
End With
End Function
and exploit it in your main code as follows:
Sub main()
Dim sht As Worksheet
Application.ScreenUpdating = False '<--| this to prevent sheet activating slow down the code (and annoy you)
For Each sht In Worksheets
SelectFirstEmptyRowInColumnWithGivenHeader sht , "Style" '<--| you can omit the 2nd parameter and it'll be assumed the default column header
Next
Application.ScreenUpdating = True '<--| get default behavior back in place
End Sub
You can just pass the desired column number to the function. Optional ByVal fromColumn As Long = 8 means that column 8 (column H) is the default column if no column number is passed when the function is called. But passing a column number will override that default.
So in this line, passing the 8 is actually not required, although probably good for clarity, and could be written like so with the same result (returning the last row for column H):
selectLastRow = SelectFirstEmptyRowInColumnH(ActiveSheet)
To change the column number to 2 (column B) for example, you would change the line like so:
selectLastRow = SelectFirstEmptyRowInColumnH(ActiveSheet, 2)
I would also recommend that you genericize the name of the function to SelectFirstEmptyRowInColumn so to avoid confusion.
This simple code will help you.
Sub FindFirstEmptyRow()
Cells(Rows.Count, 1).End(xlUp).Offset(1).Select
End Sub
☺ ☺

VBA public variables

I am having trouble with the public i as intger portion of my code.
I am using i to keep the value of my current row so i can use this range across
my program. In my for loop it increments i so it will step through a column and search for v
however when i try using "i" in another set of code "i" no longer has a value.
I am not sure how global/public variables work in VBA or what is cause this error.
the problem occurs int Sub "yes" , and sub "no"
at the code
Cells(i,lcol).value=" ok "
and
Cells(i,lcol).value = " updated "
1st set of code is as follows, which gets my value for "i"
Public V As Integer
Public i As Integer
Private Sub Enter_Click()
Dim EmptyRow As Long
'Audit will only search Master Sheet
Worksheets("Master").Activate
'Find empty row value so we can use that for limit of search
With Sheets("Master")
EmptyRow = .Range("A" & Rows.Count).End(xlUp).Row + 1
End With
'i needs to be set to minimum limit
'Begin loop of search
For i = 12 To EmptyRow + 1
If Cells(i, 1).Value = V Then 'AssetNum.Value Then
'Go to compare userform to display
Compare.AssetDisplay.Value = AssetNum.Value
Compare.LocationDisply.Value = Cells(i - 1, 2).Value
Compare.Show
End If
Next i
'If i gets to emptyrow num then go to non found asset userform
Unload Me
NonFoundAsset.Show
End Sub
Private Sub UserForm_Initialize()
'Read in value from asset num to be comapre in loop
AssetNum.Value = V
End Sub
the second set of code im trying to call "i" using the public variable and it has no value
Private Sub No_Click()
Dim ws As Worksheet
Dim lcol As Long
'Make Master Sheet Active
Worksheets("Master").Activate
Set ws = ThisWorkbook.Sheets("Master")
'Finds next empty column
With ws
lcol = .Cells(11, .Columns.Count).End(xlToLeft).Column - 1
End With
'If the displayed location is not the same as the actual location "No" will be
'selected and the Change User Form will be displayed
'The value under the current audit column will be displayed as updated
Cells(i, lcol).Value = " Updated "
Unload Me
AuditChange.Show
End Sub
Private Sub Yes_Click()
Dim ws As Worksheet
Dim lcol As Long
'Make Master Sheet Active
Worksheets("Master").Activate
Set ws = ThisWorkbook.Sheets("Master")
'Finds next empty column
With ws
lcol = .Cells(11, .Columns.Count).End(xlToLeft).Column - 1
End With
'If the location displayed is correct "Yes" will be selected and
'The value "ok" will be displayed in the current audit column
Cells(i, lcol).Value = " Ok "
Unload Me
'Returns to Assetlookup to look for a new asset
Assetlookup.Show
End Sub
I appreciate any help, Im new to VBA and don't understand why this is not working.
I believe a public variabe in a UserForm is only available if the UserForm is running (loaded). To have a truely global variable, declare it in a normal module.
Probably the variable isn't available and VB can't find it in its scope. If Tools, Options, Require variable declarations is turned OFF, VB will create a new variable with that name in the current scope. Hence it looks as if it has "lost" its value.
Tip: don't call global variables something like i, j, n etc. These are typically used as local variables for loops and counters. Use a naming convention that makes clear the variable is global. I always prefix such a variable with g for 'global', e.g.:
Public giLoopCounter As Integer;
It depends where you declare it. You have to refer to that location. So if i is in UserForm1 and you are trying to use it from another form, reference it as.
Cells(UserForm1.i,lcol).value=" ok "
If you put
Option explicit
at the top of the form you are trying to call it from it would tell you that i by itself is not defined in that scope of you code.
EDIT: For additional comments from OP. Asked if i can be public in a click event.
To my knowledge, you can't have public/global variables in an event.
You will have to use a variable local
'Public variables are declared outside (above) all subs and functions
'This will be accessible by all subs functions and events in in the forms or sheets module or wherever it is
Public i As Integer
'This will be accessible by all subs functions and events in in the CURRENT sheet or form. It is private but to the current item
Private i As Integer
Private Sub CommandButton1_Click()
Dim j As count
'Do whatever it is to get that value.
j = 5
'You can access i to use it in you click event code
msgbox i * j
'Or you can set it in the event
i = j
End Sub

Copying range of cells from one workbook to another yields 400 error

Okay, so I am relatively new to Excel VBA. I am trying to do something which seems quite simple to me and there are many, many examples of how to do it which I have read exhaustively but I cannot seem to get past this so...here goes.
I am trying to paste a range of cells from one worksheet to another in Excel Microsoft Office Professional Plus 2010. I think I have reduced the problem to the absolute simplest form possible to illustrate the problem. This is just a snippet. The VictimResults and TempWorksheet variables are set higher up. I didn't include the code because I thought it might confuse the articulation of the problem.
Dim SourceWorksheet As Worksheet
Dim TargetWorksheet As Worksheet
Dim SourceRange As Range
Dim TargetRange As Range
Set SourceWorksheet = VictimResults
Set TargetWorksheet = TempWorksheet
Set SourceRange = Cells(1, 1)
Set TargetRange = Cells(1, 1)
TargetWorksheet.Range(TargetRange) = SourceWorksheet.Range(SourceRange)
I have placed the variables SourceWorksheet, TargetWorksheet, SourceRange, and TargetRange in a watch and set a breakpoint at the last line and they are all valid objects (not null). When I step over the breakpoint I get a dialog box which simply says "400".
Any help is much appreciated.
---edit---
I have created this complete VBA file that replicates the problem. Thought that might help someone answer.
Option Explicit
Sub Main()
GetFirstWorksheetContainsName("Sheet1").Range(Cells(1, 1)).Value = GetFirstWorksheetContainsName("Sheet2").Range(Cells(1, 1)).Value
End Sub
Function GetFirstWorksheetContainsName(worksheetNameContains) As Worksheet
Dim m As Long
Dim result As Worksheet
m = 1
Do
If InStr(1, Sheets(m).Name, worksheetNameContains) Then
Set result = Sheets(m)
Exit Do
End If
m = m + 1
Loop Until m > ThisWorkbook.Worksheets.Count
Set GetFirstWorksheetContainsName = result
End Function
Here is something else I tried which yields something a little more verbose.
Option Explicit
Sub Main()
Sheets("Sheet1").Select
Range(Cells(1, 1)).Select
Selection.Copy
Sheets("Sheet2").Select
Range(Cells(1, 1)).Select
ActiveSheet.Paste
End Sub
It gives me a "Method 'Range' of object '_Global' failed" error when executing the first Range(Cells(1, 1)).Select line.
If you are trying to copy and paste why not use .copy and .pastespecial. They may slow down your code a little bit but as long as your aren't copying and pasting thousands of things it should be ok.
I'm not sure where the 400 is coming from, but the exception that is thrown is the same is in your verbose example (1004 - "Method 'Range' of object '_Worksheet' failed", and is thrown for the same reason.
The problem is how you're addressing the Range. Cells(1, 1) is implicitly set to the active worksheet, not whatever range you are passing it to as a parameter. Since you only need one cell, you can just use the .Cells property instead:
Sub Main()
GetFirstWorksheetContainsName("Sheet1").Cells(1, 1).Value = _
GetFirstWorksheetContainsName("Sheet2").Cells(1, 1).Value
End Sub
If you need to copy more than one cell, you'll have to either grab a reference to a worksheets instead of inlining the calls to GetFirstWorksheetContainsName if you use dynamic ranges:
Sub Main()
Dim source As Worksheet
Dim data As Range
Set source = GetFirstWorksheetContainsName("Sheet2")
Set data = source.Range("A1:B2")
GetFirstWorksheetContainsName("Sheet1").Range(data.Address).Value = data.Value
End Sub
Or hard code it:
Sub Main()
GetFirstWorksheetContainsName("Sheet1").Range("A1:B2").Value = _
GetFirstWorksheetContainsName("Sheet2").Range("A1:B2").Value
End Sub

Why does NumberFormat work in 'ThisWorkbook' but not in a Worksheet?

This VBA script in Excel works perfectly when entered in 'ThisWorkbook', but when I enter it in a specific WorkSheet (where I want to link it to a button), it doesn't work, it gives an error on this line:
ActiveSheet.Range(Cells(intStartRow, cell.Column), Cells(lonLowRow, cell.Column)).NumberFormat = "m/d/yyyy"
The error given is: 1004
Does anybody have an idea why and how to remedy it?
Thank you!!
Sub proefje()
Dim strTitleRow As String
Dim arrDates() As String
Dim arrTimes() As String
Dim intStartRow As Integer
Dim lonLowRow As Long
strTitleRow = "D5:AW5"
arrDates = Split("Last update,Last recovery test,Date installed,Key valid until", ",")
arrTimes = Split("Time", ",")
intStartRow = 6
lonLowRow = ActiveSheet.Range("BB3").Value + ActiveSheet.Range("BE3").Value
'Date/Time job start
ActiveSheet.Range(strTitleRow).Select
For Each cell In Selection
If IsInArray(cell.Value, arrDates) Then
ActiveSheet.Range(Cells(intStartRow, cell.Column), Cells(lonLowRow, cell.Column)).NumberFormat = "m/d/yyyy"
End If
If IsInArray(cell.Value, arrTimes) Then
ActiveSheet.Range(Cells(intStartRow, cell.Column), Cells(lonLowRow, cell.Column)).NumberFormat = "[$-F400]h:mm:ss AM/PM"
End If
Next cell
End Sub
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
In the context of a Worksheet module, the Cells object (unless otherwise qualified) refers to Me.Cells, i.e., the worksheet wherein the code resides. See this simple example which I put in the Sheet1 module, which will raise the 1004 error when Sheet2 is the ActiveSheet.
Sub sheet1Macro()
MsgBox ActiveSheet.Range(Cells(1, 1), Cells(1, 2)).Address
End Sub
You have a construct like this, which uses the ActiveSheet, which may (or may not) be the same sheet. If Me and ActiveSheet are not the same sheet, then the error is expected, since you can't define a range which spans multiple worksheets like this.
ActiveSheet.Range(Cells(intStartRow, cell.Column), Cells(lonLowRow, cell.Column)).NumberFormat = "m/d/yyyy"
End If
Resolution: put the code in a standard module, or, fully qualify your range objects like:
With ActiveSheet
.Range(.Cells(intStartRow, cell.Column), .Cells(lonLowRow, cell.Column)).NumberFormat = "m/d/yyyy"
End With
Further reading:
How to avoid using Select in Excel VBA macros