VBA code to select sells on the basis of values in them - vba

I am trying to write a small piece of code that selects all cells containing "D" in the range A1:J10. Sorry if the code below is sub-optimal for the purpose, but I am trying to use this as a means of learning the language. There seems to be no obvious error in the code (to me), but it says 'Invalid procedure call or argument' when I try to run it.
Option Explicit
Dim t As Range
Dim finalrange As Range
Sub selectallbattleships()
For Each t In Range("A1:J10")
If t.Value = "D" Then Set finalrange = Application.Union(finalrange, t)
Next t
finalrange.Select
End Sub

You have to give finalrange an initial Range, otherwise it starts off as Nothing. The code is failing at Application.Union(finalrange, t) because it's trying to Union Nothing.

There are a couple of issues here.
First your Dim statements should be inside the sub. Next, you cannot union nothing, so you need to check if finalrange has been assigned to anything yet, then decide how to treat it - in this case if it hasn't then just assign it to t, otherwise union. Lastly, you do not need 'Application.' before union.
Sub selectallbattleships()
Dim t As Range
Dim finalrange As Range
For Each t In Range("A1:J10")
If t.Value = "D" Then
If finalrange Is Nothing Then
Set finalrange = t
Else
Set finalrange = Union(finalrange, t)
End If
End If
Next t
finalrange.Select
End Sub

Related

How do I find out why I get an error when writing to an Excel cell with VBA?

I'm still fairly new to VBA and struggling with its limitations (and mine!). Here's my code:
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Variant, RowNum As Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue
On Error Resume Next
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub
When I step through the code, Excel simply exits the sub at the line DataCacheWorksheet.Cells(1, 1).Value = CacheKey, with no error. So, two questions:
What's the bug that's preventing the value from being updated?
Why does Excel ignore my On Error command?
Edit: If I run the line in the IDE's "Immediate" box, I get the error "Run-time error '1004' Application-defined or object-defined error. I get the same error regardless of the value of CacheKey (I tried Empty, 1234 and "Hello").
Edit 2: If I modify the sub so that CacheKey and CacheValue are hardcoded and the reference to DataCache is removed, and then I run the sub standalone it works. So why doesn't it work when called from another function? Is it possible that Excel is locking cells while doing calculations?
Not sure if this applies, but you mentioned you were calling this macro from another function. If you are calling it from a function, depending on how you are calling it, that would explain your problem. For example, a worksheet function entered into a cell cannot modify another cell on the worksheet. And the attempt to do so will result in the macro merely exiting at that point, without throwing a VBA error.
How to work around this depends on specifics you have yet to share. Sometimes, worksheet event code can be useful.
Ok, wasn't about to write an answer, but there are 3 things you should modify in your code:
Found As Range and not As Variant
RowNum As Long in case it's a row after ~32K
To trap errors usually On Error Resume Next won't help you, it will just jump one line of code. You need to handle the error situation.
Modified Code
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Range, RowNum As Long ' < use Long instead of Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then ' check if not found in cache (*Edit 1)
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue ' I assume you have a `Dictionary somewhere
' On Error Resume Next <-- Remove this, not recommended to use
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub

Getting rid of Merged cells With Center across selection

Hello Recently someone posted this in a comment thread in one of my previous questions. The post itself shows a code to remove merged cells and replace them with Central Across Selection
https://codereview.stackexchange.com/questions/197726/getting-rid-of-merged-cells/197730#197730
My issue is that I can't seem to get the code to work. I tried giving the code a go but am having two issues with it. Primarily the:
Sub fixMergedCells(sh As Worksheet)
and later
Set used = sh.UsedRange
Which I don't quite understand and they seem to be stopping me from applying it as a macro button. I otherwise seem to get a debug prompt saying "Method 'UnMerge' of object 'Range' failed" with regards to the line:
.UnMerge
Could you give me a hand in understanding what it is that I can't seem to grasp.
Here is my original code from my other post:
Sub fixMergedCells(sh As Worksheet)
'replace merged cells by Center Acroos Selection
'high perf version using a hack: https://stackoverflow.com/a/9452164/78522
Dim c As Range, used As Range
Dim m As Range, i As Long
Dim constFla: constFla = Array(xlConstants, xlFormulas)
Set used = sh.UsedRange
For i = 0 To 1 '1 run for constants, 1 for formulas
Err.Clear
On Error Resume Next
Set m = Intersect(used.Cells.SpecialCells(constFla(i)), used.Cells.SpecialCells(xlBlanks))
On Error GoTo 0
If Not m Is Nothing Then
For Each c In m.Cells
If c.MergeCells Then
With c.MergeArea
'Debug.Print .Address
.UnMerge
.HorizontalAlignment = xlCenterAcrossSelection
End With
End If
Next c
End If
Next i
End Sub
Sub test_fixMergedCells()
fixMergedCells ActiveSheet
End Sub
Your sub procedure isn't listed in the available 'macros' because it has a non-optional, non-variant parameter.
Try using an optional variant type parameter that can be used or, if omitted, filled with the ActiveSheet (which I assume the button is on).
Sub fixMergedCells(Optional sh As Variant)
If IsMissing(sh) Then Set sh = ActiveSheet
sh.Cells.UnMerge
End Sub
IsMissing can only be used with optional variant type parameters. Sub procedures with optional parameters are only listed as available 'macros' to be assigned to a button if the optional parameter is the variant type.

Excel VBA .Find Function changing values in selection

Heya this is probably simple but I cannot figure out what is wrong.
I am trying to do .find for a specific date and change that selection to a user input date.
I have a userform to select a date from a combobox (date1_cbo). The combobox source is linked to dates on a worksheet (Backend). There is a textbox below for writing the new date to change it to (date1_txt). I keep getting an error
object variable or with block variable not set.
I have tried a few options without any luck here is my code:
Dim selection As Range
Dim check As Boolean
'input box validation
check = IsDate(date1_txt.Value)
If check = True Then
'find cell matching combobox
With Worksheets("Backend").Range("A1:A500")
Set selection = .Find(date1_cbo.Value) 'this is the problem
selection.Value = date1_txt.Value
End With
Else
End If
Interestingly .Find returns the range or Nothing. however because the combobox is linked to the cells I am searching through this should never return nothing... I dont understand why the error is occurring.
a variable named as 'selection' is bad coding practice but totally legal. Don't use such names for the sake of clarity.
Error 91 is caused when you are trying to read a property( .value) from null object. your selection variable is null cause date formats on the sheet and combobox are different.
Just convert to date before attempting to find it in sheet.
Set selection = .Find(CDate(date1_cbo.Value)) '/ once again, selection is valid but bad name for variable.
You are using a variable named Selection. VBA uses it as well. Rename your variable to anything else, rewrite your code and it should work. Even Selection1 is quite ok:
Public Sub TestMe()
Dim selection1 As Range
With Worksheets(1).Range("A1:A500")
Set selection1 = .Find("vi")
End With
If Not selection Is Nothing Then
selection1.Value = "some other value"
End If
End Sub
To change multiple values with Find() as here - A1:A10, then some possibility is to do it like this:
Public Sub TestMe()
Dim myRng As Range
With Worksheets(1).Range("A1:A500")
.Range("A1:A10") = "vi"
Set myRng = .Find("vi")
If Not myRng Is Nothing Then
Do Until myRng Is Nothing
myRng.Value = "New value"
Set myRng = .Find("vi")
Loop
End If
End With
End Sub
It is a bit slow, as far as it loops every time and it can be improved, if the range is united and replaced at once.

Can I create a Jump table in VBA for Excel?

I wrote a simple translator / parser to process an EDI (830) document using multiple Select Case statements to determine the code to be executed. I’m opening a file in binary mode and splitting the document into individual lines, then each line is split into the various elements where the first element of every line has a unique segment identifier.
My code works perfectly as written. However, Select Case requires checking every Case until a match is found or the Case Else is executed. I’ve sequenced the Case statements in such a manner that the segments that appear most frequently (as in the case of loops), are placed first to minimize the number of "checks before code is actually executed.
Rather than using multiple Select Cases, I would prefer to determine an index for the segment identifier and simply call the appropriate routine using that index. I’ve used jump tables in C and Assembler and anticipated similar functionality may be possible in VBA.
You can do jump tables in VBA by using the Application.Run method to call the appropriate routine by name. The following code demonstrates how it works:
Public Sub JumpTableDemo()
Dim avarIdentifiers() As Variant
avarIdentifiers = Array("Segment1", "Segment2")
Dim varIdentifier As Variant
For Each varIdentifier In avarIdentifiers
Run "Do_" & varIdentifier
Next varIdentifier
End Sub
Public Sub Do_Segment1()
Debug.Print "Segment1"
End Sub
Public Sub Do_Segment2()
Debug.Print "Segment2"
End Sub
You can do this in Excel VBA, following the example below:
The example assumes you have split your EDI document into two columns, one with the 'processing instruction' and one with the data that instruction will process.
The jump table is to the right i.e. a distinct list of the 'processing instructions' plus a name of a Sub-routine to run for each instruction.
The code is:
Option Explicit
Sub JumpTable()
Dim wsf As WorksheetFunction
Dim ws As Worksheet
Dim rngData As Range '<-- data from your file
Dim rngCell As Range '<-- current "instruction"
Dim rngJump As Range '<-- table of values and sub to run for value
Dim strJumpSub As String
Dim strJumpData As String
Set wsf = Application.WorksheetFunction '<-- just a coding shortcut
Set ws = ThisWorkbook.Worksheets("Sheet1") '<-- change to your worksheet
Set rngData = ws.Range("A2:A17") '<-- change to your range
Set rngJump = ws.Range("E2:F4") '<-- change to your circumstances
For Each rngCell In rngData
strJumpSub = wsf.VLookup(rngCell.Value, rngJump, 2, False) '<-- lookup the sub
strJumpData = rngCell.Offset(0, 1).Value '<-- get the data
Application.Run strJumpSub, strJumpData '<-- call the sub with the data
Next rngCell
End Sub
Sub do_foo(strData As String)
Debug.Print strData
End Sub
Sub do_bar(strData As String)
Debug.Print strData
End Sub
Sub do_baz(strData As String)
Debug.Print strData
End Sub
Make sure that you have written a Sub for each entry in the jump table.

Run-Time error on add method VBA

My function's intent is explained in the docstring. However, when I run it, I get a "Run-Time error '91': Object variable or With block variable not set", and a line of code is highlighted, as indicated below. I cannot find the source of this issue, especially since I am rather new to VBA. Any help would be greatly appreciated!
'==============================|Range_Collection FUNCTION|=============================='
' Given a collection and a range, add each value in that range to the collection, except
' for False values.
Function Range_Collection(col As Collection, rng As Range) As Collection
Dim Val As Variant
For Each Val In rng
If Not Val.Value = False Then
col.Add Val.Value ;************** THIS CODE IS HIGHLIGHTED************
End If
Next Val
Set Range_Collection = col
End Function
Let's say your worksheet looks like this
This is what I get when I run the code
Further to my comment, see this
The syntax is col.add "Item","Key". The key has to be unique. If you have duplicate values then then use OERN as I suggested or use a unique key.
Sub Sample()
Dim c As New Collection
For Each itm In Range_Collection(c, Range("A1:A5"))
Debug.Print itm
Next
End Sub
Function Range_Collection(col As Collection, rng As Range) As Collection
Dim rVal As Variant
For Each rVal In rng
If Not rVal.Value = False Then
On Error Resume Next
col.Add rVal.Value, CStr(rVal.Value)
On Error GoTo 0
End If
Next rVal
Set Range_Collection = col
End Function
Change Dim Val As Variant to Dim Val As Range
Also, watch out for this little "potential language oddity" ... (in any language)
When you wrote:
If Not Val.Value = False Then
... then you no doubt intended to say: (note the parentheses, carefully ...)
If Not (Val.Value = False) Then
... (in other words, "not equal to") ...
But most grammars actually bind the Not operator more tightly than a relational-operator such as =, producing what could be this actual interpretation, with no syntax-errors:
If (Not Val.Value) = False Then
... (in other words, "if Val.Value is True!")
The compiler judges your statement to be "syntactically and semantically correct," as in fact it is. But its interpretation of what you have written might not be what you thought it was. If you include the parentheses as I have shown above, all ambiguity is removed.
If you want "not equal to", always use the appropriate operator.
When using logical operators, use parentheses plentifully to make your intentions clear, both to the compiler and to other people.
Oddly enough, this sort of issue led to a bug in the very first program that I ever wrote: which "was 6 lines long, took me 6 months to write (in BASIC for a timesharing computer, nevermind how long ago now), and had a bug in it."