How to set and use empty range in VBA? - excel

I would like to use empty range in following manner :
Set NewRange = Union(EmptyRange, SomeRange)
I've tried to set EmptyRange as empty range using Nothing, Empty and Null but "run-time error '5' Invalid procedure call or argument" occurs, it seems that I have to use If statement or there is other keyword which do the job ?
I can use :
If EmptyRange Is Nothing Then
Set NewRange = SomeRange
Else
Set NewRange = Union(EmptyRange, SomeRange)
End If
instead of construction:
Set NewRange = Union(EmptyRange, SomeRange)

I use this function as a replacement for Application.Union when I need to combine several range objects, where "zero or more" of the ranges might be Nothing:
Function union(ParamArray rgs() As Variant) As Range
Dim i As Long
For i = 0 To UBound(rgs())
If Not rgs(i) Is Nothing Then
If union Is Nothing Then Set union = rgs(i) Else Set union = Application.union(union, rgs(i))
End If
Next i
End Function
Example Usage:
Sub demo_union()
Dim rg1 As Range, rg2 As Range, rg3 As Range, newRg As Range
Set rg1 = Range("A1")
Set rg3 = Range("C3")
Set newRg = union(rg1, rg2, rg3)
newRg.Select
End Sub
Below is a variation that does not duplicate overlapping cells in the returned range.
Normally when combining overlapping ranges (eg., A1:B2 and B2:C3) with Application.Union (or the function above), the result will have multiple copies of the overlapping cells.
For example using,
Application.Union([A1:B2], [B2:C3]).Cells.Count '8 cells (repeats B2)
↑ ...returns 8 cells: A1 B1 A2 B2 B2 C2 B3 C3
(and a For Each loop will have 8 iterations.)
Function union2 (below) solves this issue by returning only unique cells, and also handles empty ranges (without producing an annoyingly-vague "Invalid Procedure call or argument")
Debug.Print union2([A1:B2], [B2:C3]).Cells.Count '7 cells
↑ ...returns 7 cells: A1 B1 A2 B2 C2 B3 C3
(For Each loop will have 7 iterations.)

I used an extra variable to fix this problem. I did not use If EmptyRange Is Nothing but if my counter, j, = 0 then NewRange = SomeRange. Here is my code:
Public Sub copyLineData(line_array As Variant)
' Copys data from the line into the right sheet
Dim i As Integer
Dim j As Integer
Dim line As String
Dim rgn_data As Range
Dim rgn_selected As Range
Dim table_data As ListObject
Set rgn_data = getDynamicRangeFromSheet(Worksheets("Data"), "A1")
Set table_data = Sheets("Data").ListObjects.Add(xlSrcRange, rgn_data, xlListObjectHasHeaders:=xlYes)
' Get the selected rows
For i = 0 To ArrayLen(line_array) - 1
line = line_array(i)
' Make selection
table_data.Range.AutoFilter Field:=1, Criteria1:=line
' Copy data
j = 0
For Each Row In table_data.DataBodyRange.Rows
If Row.EntireRow.Hidden = False Then
If j = 0 Then
Set rgn_selected = Row
Else
Set rgn_selected = Union(Row, rgn_selected)
End If
j = j + 1
End If
Next Row
' Copy selection
rgn_selected.Copy Destination:=Sheets(line).Range("A1")
Next i
' Remove selection
table_data.Range.AutoFilter
' Convert back to range
table_data.Unlist
End Sub

You still can use Application.Union using Null as EmptyRange.
Dim ActualRange As Range: Set ActualRange = ThisWorkbook.Sheets(1).Cells(1,1)
EmptyRange = Null
Result = Union(ActualRange, ActualRange, EmptyRange)
The trick is to supply ActualRange twice (as Union wants two mandatory parameters and don't mind if they are the same) and use third optional parameter to do the magic.
Then if you need to start from nothing you can do this:
MyRange = Null
For each Cell In SomeRange.Cells
If ThisIsMyCell(Cell) Then MyRange = Union(Cell, Cell, MyRange)
Next

The Union() method requires at least 2 named ranges. It combines the two named ranges into one master range. If your true goal is to combine SomeRange with an Empty range, then you should just write:
Set NewRange = SomeRange
Your use of the Union() method is pointless because Union() requires two DEFINED ranges.
http://msdn.microsoft.com/en-us/library/office/aa213609%28v=office.11%29.aspx

Related

Excel - Loop through table out of bounds error

my code is to run through a table and store the cell color of column D while also storing the value of in column C as another variable. These variables are used to find a shape on another the "main" tab and update that color to the color that was stored in CellColor. When I added the loop part of the code I get an out of bounds error (-2147024809 (80070057)).
Sub Update()
Dim CellColor As Long
Dim ShapeColor As Variant
Dim rng As Range, Cell As Range
Dim i As Integer
Worksheets("Sheet1").Select
Set rng = Range("C2:C100")
For i = 2 To rng.Rows.Count
CellColor = rng.Cells(RowIndex:=i, ColumnIndex:="D").DisplayFormat.Interior.Color
ShapeColor = rng.Cells(RowIndex:=i, ColumnIndex:="C").Value
Worksheets("main").Shapes(ShapeColor).Fill.ForeColor.RGB = CellColor
i = i + 1
Next
Worksheets("main").Select
End Sub
Perhaps use a For Each loop here and Offset:
Set rng = Worksheets("Sheet1").Range("C2:C100")
Dim cell As Range
For Each cell In rng
ShapeColor = cell.Value
CellColor = cell.Offset(,1).DisplayFormat.Interior.Color
Worksheets("main").Shapes(ShapeColor).Fill.ForeColor.RGB = CellColor
Next
A brief explanation of your problem:
rng.Cells(RowIndex:=i, ColumnIndex:="C")
rng.Cells(RowIndex:=i, ColumnIndex:="D")
are not the cells you think they are, because they are offsetting but starting from column C. They are actually referring to columns E and F.
As an example: ? Range("C2").Cells(1, "C").Address returns $E$2, not $C$2.
Other points:
Remove the i = i + 1. Next is what increments i.
Avoid using Select: Set rng = Worksheets("Sheet1").Range("C2:C100").

loop through each row in a given range with if statement with multiple conditions in vba

I'm trying to loop trough each row in the range J16:P19, and with every iteration, it must be checked if the value in the cell = 3, and if the text in the corresponding coloumn (range J15:J19) is present in the range ( W1:W7).
eg. If the cell (K17) in the row (J17:P17) = 3 & the corresponding coloumn name (K15) of that cell is present in the range ( W1:W7); the value of in Q17 must be substracked by 1.
This should be done for every row in the range. My code looks like this:
private Sub CommandButton2_Click()
dim rng As Range
dim i As Range
dim row As Range
Set rng = Range("j16:p19")
For Each row In rng.Rows
For Each i In row.Cells
If i.Value = 3 & Cells(i,15) %in% Range("w1:w7") Then
Cells(row,22).Value = Cells(row,17).Value -1
Else
Cells(row,22).Value = Cells(row,17).Value
End if
Next i
Next row
End sub
It works when I select the range to be one column only, and without the second part of the if statement. Do you have any suggestions on have to solve my problem? thank you in advance
Try this.
Not sure why you were referring to column 22? Also "%in%" is not valid VBA syntax. I've used Match instead (which avoids the outer loop), but you could use Find or Countif.
Private Sub CommandButton2_Click()
Dim rng As Range
Dim i As Range
Dim row1 As Range, v As Variant 'better in my view not to call a variable "row"
Set rng = Range("j16:p19")
For Each row1 In rng.Rows
v = Application.Match(3, row1, 0)
If IsNumeric(v) Then 'row contains a 3
If IsNumeric(Application.Match(Cells(row1.row,"J"), Range("W1:W7"), 0)) Then 'corresponding J column value in W1:W7
Cells(row1.row, "Q").Value = Cells(row1.row, "Q").Value - 1 'deduct 1 from Q
End If
End If
Next row1
End Sub

For loop to capture values in different named ranges

I have been looking through multiple posts on looping through multiple named ranges and returning values to another cell. Unfortunately, I am getting stuck on how to loop through two named ranges to return the value from one named range if a cell in another named range is "X".
Below are images of the named ranges with values and the intended result in cell I46. Please note there is no formula in I46.
Named Ranges: Range1 Range2
Desired result from macro aftter loop is complete:
Code:
For Each Cell In wspGen.Range("Ineligible")
If Cell.Value = vbNullString Then
LP.zPledge.Value = "Y"
wspGen.Range("A46") = "-"
wspGen.Range("AG55").Value = "X"
Else
If Cell.Value = "X" Then
wspGen.Range("AG55").Value = vbNullString
wspGen.Range("A45").Value = "N"
LP.zPledge.Value = "N"
'Copies the corresponding value from range ("IneligibilityCode")
'if there is an "X" in any of the cells in range ("Ineligible")
'to I46. This could be multiple combinations of values in range ("IneligibilityCode")
End If
End If
Next Cell
Thank you all for your assistance.
This is a simple example using a counter variable assuming both ranges are single column and aligned. It counts how many cells down the first named range
(set in variable a) it is before x is found and retrieves the value at the same position in the named range b. Note I am using implicit active sheet references and you should specify the sheet name before the named ranges.
Option Explicit
Public Sub test()
Dim a As Range, b As Range, rng As Range, counter As Long
Set a = Range("range1"): Set b = Range("range2")
For Each rng In a
counter = counter + 1
If rng = "x" Then
Range("I46") = b.Cells(counter)
Exit For
End If
Next
End Sub
Space separated list of all matches:
Option Explicit
Public Sub test()
Dim a As Range, b As Range, rng As Range, counter As Long, outputString As String
Set a = Range("range1"): Set b = Range("range2")
For Each rng In a
counter = counter + 1
If rng = "x" Then
outputString = outputString & Chr$(32) & b.Cells(counter)
End If
Next
wspGen.Range("I46") = Trim$(outputString) ' wspGen.Range("I46") is defined in your code. This is illustrative.
End Sub

Copy & Paste values if cell value = "N/A"

I want to copy and paste values to a range of cells but only if their value = "N/A". I want to leave the formulas as they are in all the cells that do not = "N/A".
In context, I have hundreds of VLOOKUPs. Example:
=IFERROR(VLOOKUP("L0"&MID(G$4,1,1)&"A0"&MID(G$4,1,1)&MID(G$4,3,2)&"-0"&$B6,Sheet1!$C:$D,2,FALSE),"N/A")
Is this possible with VBA?
First of all, you should use real error values rather than string that only look like errors. Secondly, VLOOKUP returns the N/A error directly if the lookup value is not found, so the IFERROR wrapper can be dispenced with. So the formula
=VLOOKUP("L0"&MID(G$4,1,1)&"A0"&MID(G$4,1,1)&MID(G$4,3,2)&"-0"&$B6,Sheet1!$C:$D,2,FALSE)
is sufficient as is.
To replace N/A results with error values, you can use this
Sub Demo()
Dim ws As Worksheet
Dim rngSrc As Range
Dim datV As Variant, datF As Variant
Dim i As Long
' Get range to process by any means you choose
' For example
Set ws = ActiveSheet
With ws
Set rngSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
' Copy data to variant arrays for efficiency
datV = rngSrc.Value
datF = rngSrc.Formula
' replace erroring formulas
For i = 1 To UBound(datV, 1)
If IsError(datV(i, 1)) Then
If datV(i, 1) = CVErr(xlErrNA) Then
datF(i, 1) = CVErr(xlErrNA)
End If
End If
Next
' return data from variant arrays to sheet
rngSrc.Formula = datF
End Sub
If you really want to use strings rather than true error values, adapt the If lines to suit
Rather than loop through all cells in a range, you can use SpecialCells to shorten working with the =NA()cells
This also open up a non-VBA method (if the only error cells are NA, ie no Div#/0)
The first two methods below (manual and code) deal with the situation where you only gave NA cells
the third uses SpecialCells to focus on only the cells that need to be tested, before then running a check for NA before making updates
option1
Manual selection of formula cells that evaluate to errors
Select the range of interest
Press [F5].
Click Special
Select Formulas
check only Errors
option2
VBA updating formula cells that evaluate to errors
code
Sub Shorter()
Dim rng1 As Range
On Error Resume Next
' All error formulas in column A
Set rng1 = Columns("A").SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
'update with new value (could be value or formulae)
rng1.Value = "new value"
End Sub
option 3
Test for =NA()
Sub TestSpecificRegion()
Dim rng1 As Range
Dim rng2 As Range
Dim X
Dim lngRow As Long
On Error Resume Next
' All error formulas in column A
Set rng1 = Columns("A").SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
'update with new value (could be value or formulae)
For Each rng2 In rng1.Areas
If rng2.Cells.Count > 1 Then
X = rng2.Value2
For lngRow = 1 To UBound(X, 1)
If X(lngRow, 1) = CVErr(xlErrNA) Then X(lngRow, 1) = "new value"
Next
rng2.Value = X
Else
If rng2.Value2 = CVErr(xlErrNA) Then rng2.Value = "new value"
End If
Next
End Sub

Detect non empty last cell location using Excel VBA

In my Excel sheet, I have VBA code to detect the last non-empty cell in Column A and add incremental serial number value in that cell (in below example cell A6 value should be SN104).
This processing is limited only to Column A, and in this image example first non-empty last cell is at A6, sometimes it can be after 100 cells or 1000 cells.
Is there any simple way to handle this scenario?
Public Function GetLastCell(ByVal startRng as Range) as Range
With startRng
Set GetLastCell = IIf(.Offset(1).Value = "", .Offset(0), .End(xlDown))
End With
End Function
For your example, you can define a Range variable rng, and call the above function in this way:
Dim rng as Range
Set rng = GetLastCell( Range("A1") )
Then rng is referring to the last cell of Column A
Something like
Dim lngLastUsedRow as Integer
lngLastUsedRow = Range("A65536").End(xlUp).Row
Dim lngFirstEmptyRow as Integer
lngFirstEmptyRow = Range("A65536").End(xlUp).Offset(1,0)
// do your increment
newValue = Cint(Mid(CurrentWorkSheet.Range("A" + lngLastUsedRow).Value,2)) + 1
CurrentWorkSheet.Range("A" & lngFirstEmptyRow).Value = "SN" + newValue
I don't have excel on me, I can't test it right now. But this should get you started.
Something like this which
Find the true last used cell in any Excel version, and handles a blank result
Parses the string in the last non-blank cell (handling any length of alpha then numeric)to update the next blank cell
Sub GetTrueLastCell()
Dim rng1 As Range
Dim objRegex As Object
Dim strFirst As String
Set rng1 = Columns("A").Find("*", [a1], xlFormulas)
If Not rng1 Is Nothing Then
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "^(.+?[^\d])(\d+)$"
If .test(rng1.Value) Then
strFirst = .Replace(rng1.Value, "$1")
rng1.Value = strFirst & (Val(Right$(rng1.Value, Len(rng1.Value) - Len(strFirst)) + 1))
End If
End With
Else
MsgBox "Data range is blank"
End If
End Sub
Assumptions:
Next cell in list is empty
Serial N's only have three digits after 'SN' string (i.e., if it reaches 1000, earlier ones don't need padding, like '0100'
-
Dim rAll As Range, rLast As Range, rNext As Range, iNextSN As Integer
Set rAll = Intersect(Sheet1.Cells(1).CurrentRegion, Sheet1.Columns(1)) ' Column 'A' can be contiguous with others
Set rLast = rAll.Cells(rAll.Cells.Count) ' Last cell in current list
Set rNext = rLast.Offset(1) ' Next cell below current list
iNextSN = CInt(Right(rLast.Value, 3)) ' Get value of last serial N
rNext.Value = "SN" & iNextSN + 1 ' Assemble next SN with increment
-

Resources