Selecting first empty row with specific columns for each sheet VBA - 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
☺ ☺

Related

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

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.

Using Worksheet.Change event to add a new worksheet

I have a range of cells (specifically D6:D34) where all the values in the cells have a corresponding sheet. However, since I've been just manually adding worksheets when I add a new value (or change a cell value), I'm thinking about using Private Sub Worksheet_Change(ByVal Target as Range) to allow the automatic creation of a worksheet when the cells change. This is what I've tried to use, but now I'm getting an error that the "sheet name already exists" as it looks down the whole column. I've tried using error handling to skip over ones that exist, but it ends up moving to the next one to check but leaving "Sheet1" and "Sheet2", etc. Any suggestions on how to set this up?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim hlValue As Range
For Each hlValue In Sheets(1).Range("D6:D34")
ActiveWorkbook.Sheets.Add after:=Worksheets(Worksheets.Count)
ActiveSheet.Name = hlValue
Next
End Sub
I should also say that if one of the cell value is deleted, the worksheet should be deleted as well. Some sort of If CellValue <> Exist, Delete? I couldn't find anything to use to check if it exists besides fancy functions. Should I use one of these?
EDIT: Okay, I've got this now. This should suffice.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.DisplayStatusBar = True
Application.ScreenUpdating = False 'Run faster
Application.DisplayAlerts = False 'Just in case
Dim shtName As Variant
For Each shtName In Sheets(1).Range("D6:D34")
If WorksheetExists((shtName)) Then
'do nothing
Else
ActiveWorkbook.Sheets.Add after:=Worksheets(Worksheets.Count)
ActiveSheet.Name = shtName
Application.StatusBar = "Creating new sheet for " & shtName 'Just in case it's running slowly
Sheets("Admin").Select
End If
Next
Application.StatusBar = "READY"
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Function WorksheetExists(sName As String) As Boolean
WorksheetExists = Evaluate("ISREF('" & sName & "'!A1)")
End Function
I couldn't find anything to use to check if it exists besides fancy functions. Should I use one of these?
Yes, you should! Worksheets are part of a Collection object and there is no built-in Exists (or similar) method that you can query. Such a function is not fancy :) and it would be a good introduction to using functions and/or calling other subroutines, if you're not familiar with that already.
At it's simplest:
Function SheetExists(sName As String) As Boolean
Dim w as Worksheet
On Error Resume Next
Set w = Worksheets(sName)
SheetExists = Not w Is Nothing
End Function
How this works:
If SheetExists("sheet1") Then
'Do something
Else
'Sheet doesn't exist, so do something else
End If
You pass a string value to the function as sName. THe function then returns True or False whether this sheet exists.
First, the function SheetExists attempts to set a Worksheet variable to the specified worksheet, by name. This will predictably fail if the worksheet name doesn't exist, so we use this knowledge along with the Resume Next statement. In the case of an error, w will not be assigned a worksheet and will remain a Nothing, and then we use a boolean expression (Not w Is Nothing) as the function's return value. If the sheet does exist, w will not be nothing, so the function will return True, and if the sheet doesn't exist, w will be Nothing, so the function will return False.
The function above only uses the ActiveWorkbook, so a more robust version of this would also allow you to specify a parent workbook.
Function SheetExists(sName As String, Optional wb as Workbook = Nothing) As Boolean
'This function checks whether worksheet 'sName' exists in
' workbook object 'wb'. If no parameter is passed for 'wb' then
' assume to use the ActiveWorkbook
Dim w as Worksheet
If wb Is Nothing Then Set wb = ActiveWorkbook
On Error Resume Next
Set w = wb.Worksheets(sName)
SheetExists = Not w Is Nothing
End Function
NB: There are relatively few cases where On Error Resume Next is not frowned upon, but using this in a very small and specific Function, with a well-defined purpose and expectation is OK.
Alternatively, brute force iteration over the collection's Items may also be used to query collections for existence, and this does not rely on On Error Resume Next:
Function SheetExists2(sName as String) As Boolean
Dim ws as Worksheet, ret as Boolean
For Each ws In ActiveWorkbook.Worksheets
If ws.Name = sName Then
ret = True
Exit For
End If
Next
SheetExists2 = ret
End Function

Using array to enter to rows of a column - VBA/Excel

My overall project is to have a sheet that acts as an array/repository for values to be referenced, and to provide this as part of a macro for others to use. I have other code that references this array, and in the setup macro I have a check if this sheet already exists:
Sub Detailed_Report_SS_Setup()
Application.DisplayAlerts = False
Dim ws As Worksheet
On Error Resume Next
Set ws = Sheets("Array")
On Error GoTo 0
If Not ws Is Nothing Then
Else
Sheets.Add().Name = "Array"
Populate_Array_Sheet
End If
Application.DisplayAlerts = True
End Sub
In trying to make the Populate_Array_Sheet, I attempted to manipulate some existing code I used to name sheets in a workbook. My issue is that excel is looping through my array and adding each value to every row of the column, overwriting values as it goes through the array.
I believe my issue is the bounds (i use lower and upper), though I don't know enough about this to figure out how to correct. Here is my example of the problematic code:
Sub Populate_Array_Sheet()
Dim i As Long
Dim Arf As Variant
Arf = Array("n1", "n2", "n3", ..., "n36")
For i = LBound(Arf) To UBound(Arf)
Sheets("Array").Range("A1:A36") = Arf(i)
Next i
End Sub
I'm trying to get each individual value (n1 through n36) onto its own row in the column.
you are using Range("A1:A36") you are applying the value to entire range Try this
Sub Populate_Array_Sheet()
Dim i As Long
Dim Arf As Variant
Arf = Array("n1", "n2", "n3",....., "n36")
Sheets("Array").Range("A1").Activate
For i = LBound(Arf) To UBound(Arf)
ActiveCell.Value = Arf(i)
ActiveCell.Offset(1, 0).Activate
Next i
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

VBA vlookup reference in different sheet

In Excel 2007, I am looping through the values of column 4 in Sheet 2. Still in Sheet 2, I want to output the result of my vlookup formula into column 5. The vlookup formula needs to refer to Sheet 1 where the reference columns are. In order to do so I have the following formula
Range("E2") = Application.WorksheetFunction.VLookup(Range("D2"), _
Worksheets("Sheet1").Range("A1:C65536"), 1, False)
Problem, it returns error code 1004. I read that it was because I needed to Select Sheet 1 before running the formulas such as:
ThisWorkbook.Worksheets("Sheet1").Select
But then the searched value Range ("D2") doesn't belong to Sheet 1 and it still return code 1004 after having brought Sheet 1 into view.
What is the correct way to refer to a different sheet in this case?
try this:
Dim ws as Worksheet
Set ws = Thisworkbook.Sheets("Sheet2")
With ws
.Range("E2").Formula = "=VLOOKUP(D2,Sheet1!$A:$C,1,0)"
End With
End Sub
This just the simplified version of what you want.
No need to use Application if you will just output the answer in the Range("E2").
If you want to stick with your logic, declare the variables.
See below for example.
Sub Test()
Dim rng As Range
Dim ws1, ws2 As Worksheet
Dim MyStringVar1 As String
Set ws1 = ThisWorkbook.Sheets("Sheet1")
Set ws2 = ThisWorkbook.Sheets("Sheet2")
Set rng = ws2.Range("D2")
With ws2
On Error Resume Next 'add this because if value is not found, vlookup fails, you get 1004
MyStringVar1 = Application.WorksheetFunction.VLookup(rng, ws1.Range("A1:C65536").Value, 1, False)
On Error GoTo 0
If MyStringVar1 = "" Then MsgBox "Item not found" Else MsgBox MyStringVar1
End With
End Sub
Hope this get's you started.
The answer your question: the correct way to refer to a different sheet is by appropriately qualifying each Range you use.
Please read this explanation and its conclusion, which I guess will give essential information.
The error you are getting is likely due to the sought-for value Sheet2!D2 not being found in the searched range Sheet1!A1:A65536. This may stem from two cases:
The value is actually not present (pointed out by chris nielsen).
You are searching the wrong Range. If the ActiveSheet is Sheet1, then using Range("D2") without qualifying it will be searching for Sheet1!D2, and it will throw the same error even if the sought-for value is present in the correct Range.
Code accounting for this (and items below) follows:
Sub srch()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim srchres As Variant
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet2")
On Error Resume Next
srchres = Application.WorksheetFunction.VLookup(ws2.Range("D2"), ws1.Range("A1:C65536"), 1, False)
On Error GoTo 0
If (IsEmpty(srchres)) Then
ws2.Range("E2").Formula = CVErr(xlErrNA) ' Use whatever you want
Else
ws2.Range("E2").Value = srchres
End If
End Sub
I will point out a few additional notable points:
Catching the error as done by chris nielsen is a good practice, probably mandatory if using Application.WorksheetFunction.VLookup (although it will not suitably handle case 2 above).
This catching is actually performed by the function VLOOKUP as entered in a cell (and, if the sought-for value is not found, the result of the error is presented as #N/A in the result). That is why the first soluton by L42 does not need any extra error handling (it is taken care by =VLOOKUP...).
Using =VLOOKUP... is fundamentally different from Application.WorksheetFunction.VLookup: the first leaves a formula, whose result may change if the cells referenced change; the second writes a fixed value.
Both solutions by L42 qualify Ranges suitably.
You are searching the first column of the range, and returning the value in that same column. Other functions are available for that (although yours works fine).
Your code work fine, provided the value in Sheet2!D2 exists in Sheet1!A:A. If it does not then error 1004 is raised.
To handle this case, try
Sub Demo()
Dim MyStringVar1 As Variant
On Error Resume Next
MyStringVar1 = Application.WorksheetFunction.VLookup(Range("D2"), _
Worksheets("Sheet1").Range("A:C"), 1, False)
On Error GoTo 0
If IsEmpty(MyStringVar1) Then
MsgBox "Value not found!"
End If
Range("E2") = MyStringVar1
End Sub
It's been many functions, macros and objects since I posted this question. The way I handled it, which is mentioned in one of the answers here, is by creating a string function that handles the errors that get generate by the vlookup function, and returns either nothing or the vlookup result if any.
Function fsVlookup(ByVal pSearch As Range, ByVal pMatrix As Range, ByVal pMatColNum As Integer) As String
Dim s As String
On Error Resume Next
s = Application.WorksheetFunction.VLookup(pSearch, pMatrix, pMatColNum, False)
If IsError(s) Then
fsVlookup = ""
Else
fsVlookup = s
End If
End Function
One could argue about the position of the error handling or by shortening this code, but it works in all cases for me, and as they say, "if it ain't broke, don't try and fix it".