Trying to write a Union of Ranges Function
I get `Object variable or with block not set"
I am not getting this right (I think):
With Rng
UnionRange = Intersect(ws.UsedRange, Rng.EntireColumn)
End With
Sub iUnionRange()
Dim R As Range
'Check to see if the Function is working
Set R = UnionRange("Elements", Range("A1:D1, G1:G1, I1:K1"))
R.Select
End Sub
The Function
Function UnionRange(shtName As String, Rng As Range) As Range
Set ws = ThisWorkbook.Sheets(shtName)
If Rng Is Nothing Then Exit Function
With ws.Rng
UnionRange = Intersect(ws.UsedRange, .EntireColumn)
End With
End Function
First of all, use Set keyword to assign an object to a variable, so UnionRange = should be Set UnionRange =. Specify a sheet object when you are retrieving a range, doing so it's not necessary to pass sheet name to the function since Rng.Parent returns the sheet object.
There is example below:
Sub test()
Dim Q As Range
Dim R As Range
Set Q = Sheets("Elements").Range("A1:D1, G1:G1, I1:K1")
Q.Select
Set R = UnionRange(Q)
R.Select
End Sub
Function UnionRange(Rng As Range) As Range
If Rng Is Nothing Then Exit Function
Set UnionRange = Intersect(Rng.Parent.UsedRange, Rng.EntireColumn)
End Function
Your function is returning an object so you need to use 'Set', ie:
Set UnionRange = Intersect(ws.UsedRange, .EntireColumn)
I think your code might also also throw an error if 'R.Select' is called when the "Elements" worksheet isn't active (ie the user has activated another sheet). I wonder too if you're using the Range parameter as simply an address of cells when it can do a lot more for you.
If it were me, I'd change the code to the following:
Sub iUnionRange()
Dim ws As Worksheet
Dim r As Range
' Define the worksheet
Set ws = ThisWorkbook.Worksheets("Elements")
' Call the cell selection function
Set r = UnionRange(ws.Range("A1:D1, G1:G1, I1:K1"))
' Note, if you go to the properties of the "Elements"
' worksheet, you can change its name property to,
' say, ElementsSht and simply refer to the object by that name.
' As well as being easier to code, it does protect you
' from an error if a user changes the sheet name in
' Excel.
' So you could just uncomment the following line:
'Set r = UnionRange(ElementSht.Range("A1:D1, G1:G1, I1:K1"))
' Select the target range
SafeSelect r
End Sub
Function UnionRange(target As Range) As Range
If target Is Nothing Then Exit Function
Set UnionRange = Intersect(target.Worksheet.UsedRange, target.EntireColumn)
End Function
Sub SafeSelect(target As Range)
' Check that the range object is not nothing
' and the worksheet to be selected is active
If Not target Is Nothing Then
target.Worksheet.Activate
target.Select
End If
End Sub
If you're intending to call this routine a lot then perhaps define the UsedRange outside the scope of the function as you only need to process that command once to have the range defined. And finally, be aware that you could have some empty cells selected, especially at the bottom of your used range if some columns are shorter than others.
Good luck with your project.
Related
I'm trying to use both worksheets and ranges as variables, but I'm having some problems.
If I declare a worksheet as a variable and then use the range property it works just fine.
However, when I declare a variable Range and try to use it reference it, it throws me the error 438, object doesn't have property or method.
Sub try()
Dim ws As Worksheet
Set ws = Worksheets("Code")
ws.Range("A3", "B6").Value = "sheets"
Dim r As Range
Set r = Range("D1", "F3")
Worksheets("DATOS").r.Value = "ranges"
End Sub
My end goal would be to have both the Worksheet and the range as variables, so I could reference it such as
ws.r.Value = "123"
Thanks in advance, I hope my question isn't too basic and you can help me.
When you set a Range object, it is not a universal cell address to be used like what you did, each Range refers to a specific Worksheet that you can see under its Worksheet property. (documentation)
You did not specify the Worksheet in Set r = Range("D1", "F3") so VBA assumes that you are referring to the ActiveSheet which can be anything. (which is also why you are recommended to always fully qualify your range reference)
As mentioned in your comment - Since your objective is to use the same range for multiple worksheets, you can define the range address in a String variable and use that variable as shown below:
Sub try()
Const r As String = "D1:F3"
Worksheets("DATOS").Range(r).Value = "ranges"
Worksheets("Code").Range(r).Value = "ranges"
End Sub
You can't use range variable in this way. Rather qualify range mentioning sheet name. Try below codes.
Try below codes.
Sub try()
Dim ws As Worksheet
Set ws = Worksheets("Code")
ws.Range("A3", "B6").Value = "sheets"
Dim r As Range
Set r = Worksheets("DATOS").Range("D1", "F3")
r = "ranges"
End Sub
It is not possible to refer to the range in the way you want.
If you want to use a VBA variable to refer to ranges, you can do this:
' Get a range
Set Sht1 = ThisWorkbook.Worksheets("Sheet1")
Set Rng1 = Sht.Range("A2:B2")
' Set the contents of another range to the same value
Set Sht2 = ThisWorkbook.Worksheets("Sheet2")
Set Rng2 = Sht2.Range("C2:D2")
Rng2.value = Rng1.Value
You already seem to have a grasp of doing it this way.
If you want to refer to a range by a name, here is a method that creates a named range:
' Delete the named range if it exists and create it again.
Sub CreateNamedRange(Wbk As Workbook, Txt As String, Rng As Range)
On Error Resume Next
Wbk.Names(Txt).Delete
If Err.Number <> 0 Then Err.Clear
On Error GoTo 0
Wbk.Names.Add Txt, Rng
End Sub
Here we create a named range Name1 and retrieve it using Sht.Range("Name1"):
Sub CreateNamedRangeAndUseIt()
Dim Sht As Worksheet
Dim Rng As Range
' Set the value of the range to 42
Set Sht = ThisWorkbook.Worksheets("Sheet2")
Set Rng = Sht.Range("A2")
Rng.Value = 42
' Create a name for the range
CreateNamedRange ThisWorkbook, "Name1", Rng
' Activate some other sheet to make sure it works when the
' sheet with the named range is not active.
ThisWorkbook.Worksheets("Sheet1").Activate
' Get the named range and output the value of the range to the
' immediate window.
Set Rng = Sht.Range("Name1")
' This would also work, even though the named range does not
' exist on Sheet1:
' Set Rng = ThisWorkbook.Worksheets("Sheet1").Range("Name1")
' Or this (provided you don't have several workbooks open and
' another workbook is selected)
' Set Rng = Range("Name1")
Debug.Print Rng.Value
' Then select the range.
' We must activate the sheet first to select ranges in it.
Rng.Worksheet.Activate
Rng.Select
End Sub
The named range will still exist if you close and reopen the workbook, provided you save the workbook before closing it. So you only need to name the range once.
I am trying to develop a custom function to check if the data in a listobject is filtered.
Public Function TestFiltered() As Boolean
Dim rngFilter As Range
Dim r As Long, f As Long
Set rngFilter = ActiveSheet.AutoFilter.Range
r = rngFilter.Rows.Count
f = rngFilter.SpecialCells(xlCellTypeVisible).Count
If r > f Then TestFiltered = True
End Function
However I am getting an error "Object variable not set" in Set rngFilter = ActiveSheet.AutoFilter.Range
All of my sheets will only have one listobject, but perhaps it is safer to somehow change the function to apply the range for the first listobject found in the activesheet?
The idea of multiplying the columns and the rows and comparing them with filterArea.SpecialCells(xlCellTypeVisible).Count is rather interesting. This is what I managed to build on it:
Public Function TestFiltered() As Boolean
Dim filterArea As Range
Dim rowsCount As Long, cellsCount As Long, columnsCount As Long
Set filterArea = ActiveSheet.ListObjects(1).Range
rowsCount = filterArea.rows.Count
columnsCount = filterArea.Columns.Count
cellsCount = filterArea.SpecialCells(xlCellTypeVisible).Count
If (rowsCount * columnsCount) > cellsCount Then
TestFiltered = True
End If
End Function
Here's another approach that tests a specific listobject. It first uses the ShowAutoFilter property of the ListObject to determine whether the AutoFilter is dislayed. If so, it then uses the FilterMode property of the AutoFilter object to determine whether it's in filter mode.
Option Explicit
Sub test()
Dim listObj As ListObject
Set listObj = Worksheets("Sheet2").ListObjects("Table1") 'change the sheet and table names accordingly
If IsListobjectFiltered(listObj) Then
MsgBox listObj.Name & " is filtered", vbInformation
Else
MsgBox listObj.Name & " is not filtered.", vbInformation
End If
End Sub
Function IsListobjectFiltered(ByVal listObj As ListObject) As Boolean
If listObj.ShowAutoFilter Then
If listObj.AutoFilter.FilterMode Then
IsListobjectFiltered = True
Exit Function
End If
End If
IsListobjectFiltered = False
End Function
Try along these lines
Dim i As Long
Dim isFiltered As Boolean
' test if AutoFilter has been turned on in the active sheet
If ActiveSheet.AutoFilterMode Then
' loop through the filters of the AutoFilter
With ActiveSheet.AutoFilter.Filters
For i = 1 To .Count
If .Item(i).On Then
isFiltered = True
Exit For
End If
Next i
End With
End If
This will also work if you are using Tables in Excel. I am using something like this in an If-Then statement to see if the number of rows in the first column matches the number of visible cells in the first column:
Dim tbl As ListObject
Set tbl = ActiveSheet.ListObjects("Table1")
If tbl.ListColumns(1).DataBodyRange.Rows.Count <> tbl.ListColumns(1).DataBodyRange.Rows.SpecialCells(xlCellTypeVisible).Count Then
'Do something if True
End If
This is my first post on stackoverflow. I have two sub procedures in Excel VBA. The first one, called Sub IAR_part_2(), is intended to assign two sheets (by index location) to two variables named sheetname1 and sheetname2. after assigning the variables I am trying to pass them to my second sub procedure, called IAR_macro, to be processed. The two sheets are dependant on one another, so sheets 4 and 8 are ran through the IAR macro, sheets 5 and 9, sheets 6 and 10, etc. My problem is that I cannot figure out how to pass the sheetname variables from IAR_part_2 to IAR_macro. What am I doing wrong?
Sub IAR_part_2()
sheetname1 = Worksheets(4)
sheetname2 = Worksheets(8)
Call IAR_macro
End Sub
Sub IAR_macro(sheetname1 As Worksheet, sheetname2 As Worksheet)
Dim h As Long
Dim i As Long
Dim l As Long
Dim j As Long
Dim k As Long
Dim lr As Long
Worksheets(sheetname1).Activate
' Find the number of the last cell with data in column A and subtract 1 to populate variable i
On Error GoTo Canceled
i = (Range("B1").End(xlDown).Row) - 1
'Switch over to the Code sheet
Worksheets(sheetname2).Activate
'While the number of loops is less than variable i minus 1, copy the contents of cells A2 through A29 over and over down the worksheet
Do While l < (i - 1)
Range("A2:A29").Select
Selection.Copy
lr = Cells(Rows.Count, "A").End(xlUp).Row
Range("A" & lr + 1).Select
ActiveSheet.Paste
l = l + 1
'rest of macro follows from here...
Simple example of how to pass worksheet objects to a different sub:
Sub Macro1()
'Declare variables
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Assign variables to worksheet objects
Set ws1 = Worksheets(4)
Set ws2 = Worksheets(8)
'Call the second sub and pass the worksheet variables to it
Call Macro2(ws1, ws2)
End Sub
Sub Macro2(ByVal arg_ws1 As Worksheet, ByVal arg_ws2 As Worksheet)
'Reference the accepted arguments (in this case worksheet variables) directly:
MsgBox arg_ws1.Name
MsgBox arg_ws2.Name
'This will result in an error because you're using the passed argument incorrectly:
MsgBox ActiveWorkbook.Sheets(arg_ws1).Name '<-- Results in error
End Sub
You must reference the passed arguments directly. If you want to use the structure shown in your code, then the arguments passed need to be a string (but this method is NOT recommended):
Sub Macro1()
'Declare variables
Dim sSheet1 As String
Dim sSheet2 As String
'Assign variables to worksheet objects
sSheet1 = Worksheets(4).Name
sSheet2 = Worksheets(8).Name
'Call the second sub and pass the worksheet variables to it
Call Macro2(sSheet1, sSheet2)
End Sub
Sub Macro2(ByVal arg_sSheetName1 As String, ByVal arg_sSheetName2 As String)
'Because the arguments are strings, you can reference the worksheets this way
'This method is NOT recommended
MsgBox Worksheets(arg_sSheetName1).Name
MsgBox Worksheets(arg_sSheetName2).Name
End Sub
I noticed in your examples, you have your variables declared inside the function. Normally any variables you wish to use are better implemented by using option explicit. Also when identifying sheets, you will have less problems when addressing a sheet by its sheet number as opposed to the sheet name. That way if you need to use a variable, you can use just a integer instead as well.
Option Explicit
Dim h as Long, i as Long, l as Long, j as Long, k as Long, lr as Long
Dim x as Integer
Sub IAR_macro()
On Error GoTo Canceled
i = (Range("B1").End(xlDown).Row) - 1
Sheets(x).Activate
Do While l < (i - l)
Sheet ids can be located in the development tool. Here is an example:
This is minimal way of passing the worksheets. As far as they are objects, they are passed by reference by default:
Sub TestMe()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = Worksheets(1)
Set ws2 = Worksheets(2)
Passing ws1, ws2
End Sub
Sub Passing(arg_ws1 As Worksheet, arg_ws2 As Worksheet)
Debug.Print arg_ws1.Name
Debug.Print arg_ws2.Name
End Sub
VBA is not as easy and as simple as many people (mainly those who consider it to be a funny-scripting-language, written by wanna-be-developers) think. Sometimes it allows to write ByVal, but it follows its own rules and takes the argument ByRef, just to comfort you and make sure you are not going to make an error.
Saw the answer from #tigeravatar here and I have decided not to write a comment under it, but to explain in a different post why it is wrong and dangerous, as far as explaining it as a comment would have been tough.
If you try to write a Stop line here from the answer:
Sub Macro1()
'Declare variables
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Assign variables to worksheet objects
Set ws1 = Worksheets(4)
Set ws2 = Worksheets(8)
'Call the second sub and pass the worksheet variables to it
Call Macro2(ws1, ws2)
End Sub
Sub Macro2(ByVal arg_ws1 As Worksheet, ByVal arg_ws2 As Worksheet)
'Reference the accepted arguments (in this case worksheet variables) directly:
MsgBox arg_ws1.Name
Stop
MsgBox arg_ws2.Name
'This will result in an error because you're using the passed argument incorrectly:
MsgBox ActiveWorkbook.Sheets(arg_ws1).Name '<-- Results in error
End Sub
Run the answer and wait for the Stop line:
If the arg_ws1 were taken byVal, then if someone changes the name of the 8th worksheet, while the Stop is lighting, then it should still take the old name. It is ByVal, remember? Well, go ahead and change the name. Then continue with F5. Which name are you getting? Why?
The answer is because of the way the Sub is called with parenthesis in the arguments. These force ByVal and ignore anything explicitly written.
CPearson ByRef vs ByVal
Disclaimer - My Article for how to refer a function ByVal when it is ByRef
You need to pass the two variables you created to your second sub when you call the procedure:
Sub IAR_part_2()
Set sheetname1 = Worksheets(4)
Set sheetname2 = Worksheets(8)
Call IAR_macro (sheetname1,sheetname2)
End Sub
I've been searching how to assign a row to a variable and manipulate cells through the variable but I can't seem to find how to do this.
x = Sheet5.Range("A1").EntireRow
MsgBox x(1, 1)
The above code will get me the row into 'x', but is there any way that I can change a cells value using the variable 'x'? x(1,1) = "foo" will not work, and since it's not an object I can't access .Value.
Here's some sample code:
Sub Ranging()
Dim rng As Excel.Range
Dim ws As Excel.Worksheet
Set ws = ActiveSheet
Set rng = ws.Range("A1").EntireRow
With rng
Debug.Print .Cells(1).Value
Debug.Print .Cells(5).Address
.Cells(43).Value = "SurfN'Turf"
End With
End Sub
Debug.Print prints to the VBE's Immediate Window (access with Ctrl-G)
I am new in VBA coding. Lets say I am retrieving value from Sheet3.Cell(23, 4) for a value, is there any way in the VBA code which let me set this as a variable?
For example, I have changed the interface and let the value stay at Sheet4.Cell(20,1), everywhere in my code which refer to Sheet3.Cell(23, 4) need to be changed to Sheet4.Cell(20, 1). I am thinking is there any best practice for coding VBA for situation like this?
Yes. For that ensure that you declare the worksheet
For example
Previous Code
Sub Sample()
Dim ws As Worksheet
Set ws = Sheets("Sheet3")
Debug.Print ws.Cells(23, 4).Value
End Sub
New Code
Sub Sample()
Dim ws As Worksheet
Set ws = Sheets("Sheet4")
Debug.Print ws.Cells(23, 4).Value
End Sub
Yes, set the cell as a RANGE object one time and then use that RANGE object in your code:
Sub RangeExample()
Dim MyRNG As Range
Set MyRNG = Sheets("Sheet1").Cells(23, 4)
Debug.Print MyRNG.Value
End Sub
Alternately you can simply store the value of that cell in memory and reference the actual value, if that's all you really need. That variable can be Long or Double or Single if numeric, or String:
Sub ValueExample()
Dim MyVal As String
MyVal = Sheets("Sheet1").Cells(23, 4).Value
Debug.Print MyVal
End Sub