VBA Range is Nothing - vba

Running the code below and what I'm hoping to see as a result is to have all columns with 0s on row 27 hidden - but depending on the frequency of the data, the range for those columns to be hidden is different. Basically anything that is in a sheet that starts with Daily/monthly/weekly will have to have columns hidden, all the rest of the sheets should be left alone.
It worked on a simple workbook using an if statement (sheets starting with X vs. all other), but when I added the case statement it broke...
The line marked down with bold is the one where I get an error:
Run-time error '1004'
Application-defined or object-defined error
I'm new to this, so please feel free to suggest a good vba tutorials website/book.
Sub Hide_Zero_Columns()
Dim WS As Worksheet
Dim Col_to_hide As Range
Dim Range_to_hide As Range
Dim X As Integer
For Each WS In ThisWorkbook.Sheets
Worksheets(WS.Name).Activate
With WS
Select Case Data_Frequency_Sheets
Case Left(WS.Name, 5) = "Daily"
Set Range_to_hide = Range("BDV$27:CWH$27")
Case Left(WS.Name, 5) = "Month"
Set Range_to_hide = Range("AY$27:CO$27")
Case Left(WS.Name, 5) = "Weekl"
Set Range_to_hide = Range("HF$27:NN$27")
Case Else
Set Range_to_hide = Range("A1:B1")
End Select
Select Case Data_Condition
Case Left(WS.Name, 5) = "Daily"
X = 1
Case Left(WS.Name, 5) = "Month"
X = 30
Case Left(WS.Name, 5) = "Weekl"
X = 7
Case Else
X = 999
End Select
If X <> 999 Then
For Each Col_to_hide In ActiveSheet.Range(Range_to_hide) '<-- Error here
If UCase(Col_to_hide) = 0 Then
Col_to_hide.EntireColumn.Hidden = True
Else: Col_to_hide.EntireColumn.Hidden = False
End If
Next Col_to_hide
End If
End With
Next
ActiveWorkbook.Worksheets("Registrations").Activate
End Sub

Since you have already defined a Range, you the problem is you are trying to evaluate: Sheet.Range(Range) which throws the error you are getting.
Since it appears you are wanting to iterate across the columns, all you need to do is change the line to this:
' Iterate across the columns in the defined range.
For Each Col_to_hide In Range_to_hide.Columns
' Each "Col_to_hide" will represent all cells within the column.
MsgBox Col_to_hide.Address

The error you're getting is because you're passing a Range object as the argument to Activesheet.Range() and it doesn't accept that because of the Range object's default value. It would be like doing this in the Immediate Window
?Range(Range("A1")).Address
You'll get the same error. Whereas with
?Range("A1").Address
You don't. You could do this too
?Range(Range("A1").Address).Address
So the thing is that when you don't specify a property for an object, like Range("A1") instead of Range("A1").Address, then the default property is used. The Range object is a bit strange, but in this case its default property is the Value property. So Activesheet.Range(Range_to_hide) is the same as Activesheet.Range(Range_to_hide.Value). And if Range_to_hide is a multi-cell range, then it's Value property returns an array, which you definitely can't pass into a Range's argument.
That's the explanation, the short answer is simply to use Range_to_hide, like
For Each Col_to_hide in Range_to_hide.Columns
Why the .Columns. Remember when I said that the Range object was a little strange. Well, unlike most objects, it has two default properties based on the context you're using it in. Earlier, the default property was Value, but in the context of a For..Each, the default value is Cells. If you don't specify .Columns in your For..Each, then it will loop through every cell in the Range. In fact, I always specify how the range is aggregated, even if it's the default Cells.

Related

Finding dates and storing ranges in variables

I'm trying to find "blocks" in the worksheet that have dates in the A-column. A block is seperated by lines as you can see from the picture. The whole file is full of these blocks but I only need the blocks that have dates in the A-column. Just to be clear I don't just need the rows with the dates but the full block that contains the date.
A block in my files for example is the Range A172:G192.
Picture of the file:
[![enter image description here][1]][1]
How should I continue after selecting the first block? I probably should use the Find function starting from row 184 or the row of ResultDown variable moving down the sheet on "A" Column. However the row needs to be dynamic, so I can use it for the next block. Also I have no idea how many blocks there will be, so I would like to hear from you how to solve this aswell.
I would like to save all the blocks as different variables and then hide all the blocks in the worksheet and then just unhide the blocks that I stored in the variables.
My biggest problem is the last row.
Result2 = Range(Cells(StartcellRow, 1), Cells(1000, 1)).Find(What:="**/**/****", After:=Range(Cells(StartcellRow, 1))).Select
I keep getting an runtime error 1004
Public Sub FB_MAKRO()
Dim FBwb As Workbook
Dim FBsht As Worksheet
Dim ACol As Range
'Set variables for workbook and sheet
Set FBwb = Workbooks.Open(Filename:="C:\Users\l000xxx\Desktop\Makrot\FORCED BALANCE MAKRO\FB HARJOITUS.xls")
Set FBsht = FBwb.Sheets("Forced Balance")
Set ACol = FBsht.Range("A1:A1000")
'I want ACol variable to be the entire A-Column. Any ideas?
'For some reason the range function is not working here?
'This locates the date in A'column, so I can select the correct block
Result = Range("A3:A1000").Find(What:="**/**/****", After:=Range("A3")).Address
'This is the top left corner of the Block1 selection
ResultUp = Range(Result).End(xlUp).Offset(-1, 0).Address
Range(ResultUp).End(xlDown).Select
Range(ActiveCell, ActiveCell).End(xlDown).Select
'The ResultsDownLastRow variable is for Block2 find function
'ResultDown is the bottom right corner of the Block1
ResultsDownLastRow = Range(ActiveCell, ActiveCell).End(xlDown).Address
ResultDown = Range(ActiveCell, ActiveCell).End(xlDown).Offset(-2, 6).Address
'First Block assigned. I plan to use this in the end when I hide everything and then unhide these blocks
Block1 = Range(ResultUp, ResultDown).Select
' NEXT BLOCK STARTS HERE
'StartCellRow is the cell that the find function should start looking for Block2
'Result2 is the find function for Block2
StartcellRow = Range(ResultsDownLastRow).Row
Result2 = Range(Cells(StartcellRow, 1), Cells(1000, 1)).Find(What:="**/**/****", After:=Range(Cells(StartcellRow, 1))).Select
End Sub
'This returns value 194
StartcellRow = Range(ResultsDownLastRow).Row MsgBox StartcellRow
'This should work but doesn't. I get a syntax error
Range(Cells(StartcellRow &","& 1),Cells(1000 & "," & 1)).Find(What:="**/**/****", After:=Range(Cells(StartcellRow& ","& 1)).Select
This doesn't work either
'StarcellRow gives out value of 194
StartcellRow = Range(ResultsDownLastRow).Row
Result2 = Range("A&:StartcellRow:A648").Find(What:="**/**/****", After:=Range("A&:StartcellRow")).Select
This doesn't give me a syntax error but it's not working
I would search for all currency header and store their rownumber into an array. For each rownumber in the array i would look into the cell below (rownumber + 1). when there is a date in the cell, i would set the range in the following way:
set rangeWithDate = Range(Cells(actualRowNumberInArray - 1, 1), Cells(nextRowNumberInArray - 2, 7))
Array:
Dim array1() As long
Redim array1(5)
For i = 1 To 5
array(i) = i
Next i
Redim array1(10) ' changes the "length" of the array, but deletes old entries
For i = 1 To 10
Feld1(i) = i
Next i
Redim Preserve array1(15) ' changes the "length" of the array without deleting old entries

VLookUp not working - Property could not be assigned

My problem is, that when using the VlookUp I do get the Error:
The VLookup-Property of the WorksheetFunction-Object could not be assigned
' LookUp missing Data
Dim lookupRange As Range
Set lookupRange = Sheets("Kundenlisten HLK 2018").Range("A1:Y10354")
' Fill in Companyname
Dim tmp As String
tmp = Values(0)
tmp = TrueTrim(tmp)
testing.Cells(8, counter) = Application.WorksheetFunction.VLookup(tmp, lookupWS.Range("A2:Y10354"), 2, False)
Values = None
counter = counter + 1
lookupWS is the Name of the Worksheet
As you can see the table I am trying to lookup is filled with values from A to Y. The first column is the key I am trying to look up, but then the error from above occurs. The tmp variable is a String with a unique ID to look up the missing values, while the "2" is supposed to be the company name in the second column of the Range.
I looked up on the docs, but the types and everything are fine, I even checked while debugging.
testing.Cells(8, counter) can't be the source of the problem aswell, since I am using it before like this
testing.Cells(28, counter) = Left(mail.ReceivedTime, 10)
and it works
It's difficult to know what the problem is without any data, but here's something to help you in the right direction.
It's better to use Find and Offset than
WorksheetFunction.Vlookup in VBA
Something like this gives you exactly the same result, but you have much more control:
Sub Test()
Dim valueFound As Range
Set valueFound = lookupWS.Range("A2:A10354").Find(What:="Something", lookat:=xlWhole) 'xlWhole is equivalent to FALSE in VLOOKUP
If valueFound Is Nothing Then
MsgBox "Nothing found"
Else
MsgBox valueFound.Offset(0, 1) 'offsetting by 1 is equivalent to using 2 in a VLOOKUP
End If
End Sub

VBA Range using a variable to set a location

I want to have my program begin a count at a specific location defined by a variable. I know that you can use rn = sh.Cells(9, 1) for example, and the variable rn is now holding the location of cells(9,1). But when I use the variable rn in my count command, I am getting the following error:
Method 'Range' of object' _worksheet' failed
Here's my code:
Option Explicit
Sub Struc_Equa()
Dim n As Integer
End sub
Sub countN(n)
Dim sh As Worksheet
Dim rn As Range
Dim SheNa As String
SheNa = ActiveSheet.Name
Set sh = ThisWorkbook.Sheets(SheNa)
Set rn = sh.Cells(9, 1)
' The command below does not work
'n = sh.Range(rn, sh.Range(rn).End(xlToRight)).Columns.Count
' I am able to do what I want but in a inefficient way (shown below)
'n = sh.Range("A9", sh.Range("A9").End(xlToRight)).Columns.Count
rn.Activate
MsgBox (n)
End Sub
Why is this error happening?
Try this:
n = sh.Range(rn, rn.End(xlToRight)).Columns.Count
It follows the Range([Cell1],[Cell2]) syntax.
Remember that rn is already a fully referenced range address so you can use it directly as arguments to Range object. More about Range Syntax.
Why does your original code fail?
Because based on this article, in Range(arg) syntax, arg names the range.And take note that Range Object's default property is Value.
So your code:
sh.Range(rn).End(xlToRight)
is actually evaluating the value of rn and supplies it as arg for the Range(arg) syntax. If the value of that cell is not a valid cell or range address (or name as the article mentioned), it will throw an error. Above actually evaluates like below:
sh.Range("value from rn range object").End(xlToRight)
If you try changing the value of that cell with a valid cell or range name/address, it will work. For example:
With sh
Set rn = .Cells(9, 1)
rn.Value2 = "A9"
n = .Range(rn, .Range(rn).End(xlToRight)).Columns.Count
MsgBox n
End With
In short:
Range(arg) expects a valid range name or address.
Range([cell1],[cell2]) expects a valid range object reference.

UDF returns the same value everywhere

I am trying to code in moving average in vba but the following returns the same value everywhere.
Function trial1(a As Integer) As Variant
Application.Volatile
Dim rng As Range
Set rng = Range(Cells(ActiveCell.Row, 2), Cells(ActiveCell.Row - a + 1, 2))
trial1 = (Application.Sum(rng)) * (1 / a)
End Function
The ActiveCell property does not belong in a UDF because it changes. Sometimes, it is not even on the same worksheet.
If you need to refer to the cell in which the custom UDF function resides on the worksheet, use the Application.Caller method. The Range.Parent property can be used to explicitly identify the worksheet (and avoid further confusion) in a With ... End With statement.
Function trial1(a As Integer) As Variant
Application.Volatile
Dim rng As Range
with Application.Caller.Parent
Set rng = .Range(.Cells(Application.Caller.Row, 2), _
.Cells(Application.Caller.Row - a + 1, 2))
trial1 = (Application.Sum(rng)) * (1 / a)
end with
End Function
You've applied the Application.Volatile¹ method but allowed the range to be averaged to default to the ActiveSheet property by not explcitly specifying the parent worksheet.
The average is computed with the Excel Application object returning a SUM function's result and some maths. The same could have been returned in one command with the worksheet's AVERAGE function but blank cells would be handled differently.
trial1 = Application.Average(rng)
¹ Volatile functions recalculate whenever anything in the entire workbook changes, not just when something that affects their outcome changes.
It's kind of strange to me for a UDF to calculate moving average given a number. If this UDF is to be used within the Worksheet, I believe you would put it next to existing data and if you want to change the size of the range for average amount, you update them manually?
Assuming you can name a Range "MovingAverageSize" to store the size of the range to calculate the average, and the average amount on the right of the existing data, consider below:
Range C2 is named MovingAverageSize
Data stored from B3 and downwards
Moving Average result is stored 1 column on the right of the data
If the data is less than MovingAverageSize, the SUM function adjusts accordingly
Any calculation error occurs with result in zero
Every time MovingAverageSize changes value, it triggers a Sub to update the formulas (Codes are placed in the Worksheet object rather than normal Module)
Alternatively, you can change the code to place the MovingAverage to same column of the MovingAverageSize, so you can have a few different size comparing next to each other.
Code in Worksheet Object:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count = 1 Then
If Target.Address = ThisWorkbook.Names("MovingAverageSize").RefersToRange.Address Then UpdateMovingAverage Target
End If
End Sub
Private Sub UpdateMovingAverage(ByRef Target As Range)
Dim oRngData As Range, oRng As Range, lSize As Long, lStartRow As Long
Debug.Print "UpdateMovingAverage(" & Target.Address & ")"
If IsNumeric(Target) Then
lSize = CLng(Target.Value)
If lSize <= 0 Then
MsgBox "Moving Average Window Size cannot be zero or less!", vbExclamation + vbOKOnly
Else
' Top Data range is "B3"
Set oRngData = Target.Parent.Cells(3, "B") ' <-- Change to match your top data cell
lStartRow = oRngData.Row
' Set the Range to last row on the same column
Set oRngData = Range(oRngData, Cells(Rows.Count, oRngData.Column).End(xlUp))
Application.EnableEvents = False
For Each oRng In oRngData
If (oRng.Row - lSize) < lStartRow Then
oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & lStartRow - oRng.Row & "]C[-1]:RC[-1])/MovingAverageSize,0)"
Else
oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & 1 - lSize & "]C[-1]:RC[-1])/MovingAverageSize,0)"
End If
Next
Application.EnableEvents = True
Set oRngData = Nothing
End If
End If
End Sub
Sample data and screenshots
I believe that Application.ActiveCell is not what you should be using here.
Application.ThisCell would be more appropriate assuming that "a" is the size of the subset and that the dataset is 1 column on the right.
Moreover, I would simply use "WorksheetFunction.Average" instead of "Application.Sum" and I would add "Application.Volatile" so the average is recalculated whenever an update occurs on the worksheet.
So one solution to your issue would be:
Public Function Trial1(a As Integer) As Variant
Application.Volatile
Trial1 = WorksheetFunction.Average(Application.ThisCell(1, 2).Resize(a))
End Function
Another solution here would be to use an array formula entered with Control/Shift/Enter:
Public Function MovAvg(dataset As Range, subsetSize As Integer)
Dim result(), subset As Range, i As Long
ReDim result(1 To dataset.Rows.count, 1 To 1)
Set subset = dataset.Resize(subsetSize)
For i = 1 To dataset.Rows.count
result(i, 1) = WorksheetFunction.Average(subset.offset(i - 1))
Next
MovAvg = result
End Function
And to use this array function:
Select the range where all the results will be written (should be the size of your dataset)
Type "=MovAvg(A1:A100, 2)" where A1:A100 is the source of the data and 2 the size of the subset
Press Ctrl+Shift+Enter
A UDF should only access a range when it is passed as a parameter.
Also, you should eliminate Application.Volatile because (1) your calculation is deterministic and not volatile, (2) Excel will re-calculate automatically your UDF whenever any cell in the input range changes, and (3) because the 'volatile' attribute in a UDF can make a model very slow so it should avoided when not necessary.
So, for a moving average, the correct formula is:
Public Function SpecialMovingAverage(Rng as Excel.Range) As Double
Dim denominator as Integer
denominator = Rng.Cells.Count
if Denominator = 0 then SpecialMovingAverage = 0: exit function
' write your special moving average logic below
SpecialMovingAverage = WorksheetFunction.Average(Rng)
End Function
Note: I changed the answer following two comments because I initially did not see that the question was after a moving average (maybe the question was changed after my answer, or I initially missed the UDF's stated objective).
I believe
Your trial1() function is in one or more cells, as a part of a formula or by itself
You want those cells to be recalculated whenever the user changes any cell on the worksheet
For this, you'd need to identify the cell where the change happened. This cell is not given by
A. ActiveCell - because that is the cell the cursor is on when the calculation starts; it could be anywhere but not on the cell that was changed
B. Application.ThisCell - because that returns the cell in which the user-defined function is being called from, not the cell that changed
The cell where the change happened is passed to the Worksheet's Change event. That event is triggered with an argument of type Range - the range that changed. You can use that argument to identify the cell(s) that changed and pass that to trial1(), possibly through a global variable (yeah, I know).
I tried this in a worksheet and it works, so let me know your results.

Range object returns empty values

I have a set range to a variable in this fashion:
Dim srcRng As Range
Set srcRng = Range(hrwb.Worksheets(1).Range(yomColAddress)(1).Address, _
Cells(hrwb.Worksheets(1).Range(yomColAddress).row + 200, rightMostCol)(1).Address)
for some weird reason when I call
srcRng(1) 'actually instead of 1 is i that runs 1 to srcRng.Count
it doesn't return the upper leftmost cell value. Any ideas why?
(for those who are not familiar with this technique: http://www.cpearson.com/excel/cells.htm)
Informations:
at execution time the variables yomColAddress=$AL$9 and righMostCol=40
hrwb.Worksheets(1).Range(yomColAddress)(1) works as expected.
With MsgBox srcRng(1).Address & " value:" & srcRng(1).Value I get "$AL$9 value:"
The value of AL9 is the text "yom"
The actual code is:
Dim srcRng As Range
Set srcRng = Range(hrwb.Worksheets(1).Range(yomColAddress)(1).Address, Cells(hrwb.Worksheets(1).Range(yomColAddress).row + 200, rightMostCol)(1).Address)
Dim i As Integer
i = 1
While (weekDayCol = 0 And i <= srcRng.count)
If loneHebDayLetter("à", "ä", srcRng(i)) Then'loneHebDayLetter checks some conditions on a cell
weekDayCol = srcRng(i).Column
End If
i = i + 1
Wend
I think I get what goes wrong here:
The code itself is working well but not on the good data (This is a supposition but I just did some tests with a custom workbook)
Short version
Just add srcRng.Select after Set srcRng (no real interest but to understand what it does) and I think you will get what happens if my supposition is correct.
Longer version
When you do Set srcRng = ... it does create the correct Range but it is not linked to any sheet actually ... It just means remember a Range which goes from cell X to cell Y.
The point is: The sheet (let's say "sheet2") where your code is executed isn't the same as the one where the datas are (say "sheet1") so srcRng(1) is understood as Sheets("sheet2").srcRng(1) instead of Sheets("sheet1").srcRng(1) (<- that's what you want)
Even if not elegant, this should work:
Dim srcRng As Range
With hrwb.Worksheets(1)
Set srcRng = Range(.Range(yomColAddress)(1).Address, Cells(.Range(yomColAddress).row + 200, rightMostCol)(1).Address)
Dim i As Integer
i = 1
While (weekDayCol = 0 And i <= srcRng.count)
If loneHebDayLetter("à", "ä", .Range(srcRng.Address)(i).Value) Then 'I assume it take the value not the cell: if it take the cell you may get an error!
weekDayCol = srcRng(i).Column
End If
i = i + 1
Wend
End With
What is important is the use of .Range(srcRng.Address)(i).Value to access the value in the right worksheet! (That's why this trick is not needed here: srcRng(i).Column because colum numbers do not change from one sheet to an other)
(NOTE: I used with to optimize/clarify the code)
If something isn't clear tell me