VBA Dim and Set Range global - vba

I have a probably easy problem using variables globally:
I want to Dim and Set various ranges in order to use them in different subs.
Simple Example given:
Sub Variables()
Dim rng1 as Range
Set rng1 = Worksheets("Sheet1").Cells
Dim rng2 as Range
Set rng2 = Worksheets("Sheet2").Cells
End Sub
Then in order to use my variables in other subs I tried to call my Variable sub, like:
Sub calc()
Call Variables
Dim i as Integer
i = rng1.Find("Hello").Column
With Sheet1.Cells(1, i)
.Value = "World"
End With
I get the Error "Object required". I also tried to define my Variables as Public Sub but it still doesn't work.
I hope you know how to solve this problem or how to give a different approach!
Thanks!

Option Explicit
Dim rng1 As Range
Dim rng2 As Range
Sub Variables()
Set rng1 = Worksheets("Sheet1").Cells
Set rng2 = Worksheets("Sheet2").Cells
End Sub
Sub calc()
Variables
Dim i As Integer
i = rng1.Find("Hello").Column
Sheet1.Cells(1, i).Value = "World"
End Sub
You declared inside the Sub, you can't able to use them in other Sub, so that's why it gives you an error.
The solution here is put them at the top of your Procedure.

Related

Application.Caller / Storing worksheet name as string

Im trying to store the CurrentWorksheet name in order to reference it in a different Sub routine.
The code I currently have is as follows:
Private Sub InsertNewBill_Click()
Dim rng As Range
Set rng = Worksheets(CurrentWorksheet).Range("A30:L30")
rng.Insert Shift:=xlDown
End Sub
Current Worksheet Function:
Function CurrentWorksheet()
CurrentSheet = Application.Caller.Worksheet.Name
End Function
I need to try to reference the "CurrentSheet" variable in the "InsertNewBill" Sub routine.
The function of this is to insert a new line of cells between "A30:L30" on the currently selected worksheet.
Thanks in advance
I didn't plan to write this as answer, but to explain to Batteredburrito how to deal with objects rather than names:
Option Explicit
Private Sub InsertNewBill_Click()
Dim rng As Range
Set rng = currentWorksheet.Range("A31:AC31")
rng.Insert Shift:=xlDown
End Sub
Current Worksheet Function
Function currentWorksheet() As Worksheet
set currentWorksheet = ActiveSheet
End Function
This is doing exactly the same as your version (so you can stick with that), it's just to show how to deal with objects. But there are situations where it is much better to deal with objects that with name - especially if you are dealing with multiple Workbooks (that might have the same sheet names).
And, of course, in the end you could get rid of the function completely by simply writing
...
Set rng = ActiveSheet.Range("A31:AC31")
...
Thank you all for your help and direction.
I have resolved the issue with the following
Insert new cells between a range of cells on button press:
Private Sub InsertNewBill_Click()
Dim rng As Range
Set rng = Worksheets(CurrentWorksheet).Range("A31:AC31")
rng.Insert Shift:=xlDown
End Sub
Current Worksheet Function:
Function CurrentWorksheet()
CurrentWorksheet = ActiveSheet.Name
End Function

How can I optimise Range Set and .Value

Is there a more efficient way of writing the following:
Private Sub ConvertDatesToValue_Click()
Dim Rng1 As Range, Rng2 As Range, Rng3 As Range, Rng4 As Range
Set Rng1 = Range("Q8:Q12")
Set Rng2 = Range("Q16:Q20")
Set Rng3 = Range("T8:T12")
Set Rng4 = Range("T16:T20")
Rng1.Value = Rng1.Value
Rng2.Value = Rng2.Value
Rng3.Value = Rng3.Value
Rng4.Value = Rng4.Value
End Sub
It looks and feels a bit janky.
Private Sub ConvertDatesToValue_Click()
So we're looking at a control's Click handler, presumably an ActiveX button on a worksheet, in which case we're in that worksheet's code-behind module.
The button means to invoke a command that will convert the formulas with their value in a specific range of cells that contain dates.
I would start with a procedure that's responsible for assigning Range.Value onto itself, given a Range:
Public Sub FreezeFormulaResult(ByVal target As Range)
target.Value = target.Value
End Sub
Next we need to identify the range we'll pass into that procedure.
Dim Rng1 As Range, Rng2 As Range, Rng3 As Range, Rng4 As Range
Set Rng1 = Range("Q8:Q12")
Set Rng2 = Range("Q16:Q20")
Set Rng3 = Range("T8:T12")
Set Rng4 = Range("T16:T20")
Kudos for avoiding the implicit Variant trap, and declaring an explicit type for every one of these variables.
it looks and feels a bit janky
That's because the variables have that numeric suffix. Rng1...RngN is a code smell indeed: it's a dirty solution to the problem of needing a bunch of something.
Often, a more elegant solution would be to use an array:
Dim ranges As Variant
ranges = Array(Range("Q8:Q12"), Range("Q16:Q20"), Range("T8:T12"), Range("T16:T20"))
There are different many ways to skin a cat, but a union'ed disjointed Range1 will not produce the expected results. Because you need 4 distinct areas, you'll need 4 distinct operations.
How the click handler needs to fetch the ranges, depends on where that handler is.
If we're looking at an MSForms.CommandButton (ActiveX) button on a Worksheet, then the click handler is in the very same sheet we want to get the cells from.
In that case we can work off the current object, Me - and in fact by not qualifying Range calls we're doing exactly that... implicitly.
In other words this:
Set Rng1 = Range("Q8:Q12")
Means this:
Set Rng1 = Me.Range("Q8:Q12")
You can't have a button Click handler in a standard module, but if you were to write this in one:
Set Rng1 = Range("Q8:Q12")
Then that would be implicity this:
Set Rng1 = ActiveSheet.Range("Q8:Q12")
Note the difference: that's why implicit code is evil, and why context is everything - by writing explicit code, you reduce the cognitive load by making the context local rather than ambient.
We need something that gives us an array of Range objects to work with. Let's try abstraction - it could be a public property in the sheet's code-behind:
Public Property Get ImportantDateRanges() As Variant
ImportantDateRanges = Array( _
Me.Range("Q8:Q12"), _
Me.Range("Q16:Q20"), _
Me.Range("T8:T12"), _
Me.Range("T16:T20"))
End Property
And now the button's Click handler no longer needs to care what the cells are, and the abstraction level is just perfect:
Private Sub ConvertDatesToValues_Click()
FreezeDateFormulas
End sub
Private Sub FreezeDateFormulas()
Dim dateRanges As Variant
dateRanges = Me.ImportantDateRanges
Dim i As Long
For i = LBound(dateRanges) To UBound(dateRanges)
FreezeFormulaResult dateRanges(i)
Next
End Sub
If we're looking at an MSForms.CommandButton on a UserForm, it's the exact same ActiveX button, ...but it's an entirely different story, because while you don't own a Worksheet instance (Excel does), you do own a UserForm instance - and that comes with implications too numerous to explain here (that link goes to an article I wrote about how forms don't need to run the show).
1How can I optimise Range Set and .Value
Try a direct value reversion.
Private Sub ConvertDatesToValue_Click()
With Worksheets("sheet9")
.Range("Q8:Q12") = .Range("Q8:Q12").Value
.Range("Q16:Q20") = .Range("Q16:Q20").Value
.Range("T8:T12") = .Range("T8:T12").Value
.Range("T16:T20") = .Range("T16:T20").Value
End With
End Sub
You really should be aware of what worksheet you're on.
You can loop through the 4 areas :
Dim r As Range
For Each r In Range("Q8:Q12,Q16:Q20,T8:T12,T16:T20").Areas
r = r.Value
Next

Is there a way to set a variable equal to the cell address of a comment in Excel?

I want some code that will search a worksheet for comments and return the address of a cell containing a particular comment.
Here's what I have so far:
Public Sub CommentLocator()
Dim Sht As Worksheet
Dim cmt As Comment
Dim StartCell As Variant
Set Sht = Sheet06
For Each cmt In Sht.Comments
If cmt.Text = "$StartCell" Then
Set StartCell = Range(cmt.Parent.Address)
Debug.Print cmt.Parent.Address
End If
Next cmt
End Sub
The problem is that this sets my variable StartCell to the value contained within the cell. But, I want it instead to return the address of the cell.
I've tried getting rid of the Range, but this results in a "Run-time error 13: Type mismatch".
I've tried adding .Address after the Range or the Range(cmt.Parent.Address), but this results in a "Compile Error: Argument not optional" or a "Compile Error: Object required".
I feel like I'm close, but can't quite get this to work on my own.
I'd really appreciate any help anyone can give me here. Thanks.
Don't use the Range() there:
Set StartCell = cmt.Parent
As a String:
Startcell = cmt.Parent.Address
Personally, I would favor storing the range object itself rather than the address. But if you want the described solution, this will work:
Public Sub CommentLocator()
Dim Sht As Worksheet
Dim cmt As Comment
Dim StartCell As Variant
Set Sht = Worksheets("Sheet06")
For Each cmt In Sht.Comments
If cmt.Text = "$StartCell" Then
StartCell = CStr(cmt.Parent.Address) ' <-- cmt.Parent is already a range object, no need for anything else. You can't use the Set keyword, though.
Debug.Print StartCell
End If
Next cmt
End Sub
To store the range object instead, change to:
Public Sub CommentLocator()
Dim Sht As Worksheet
Dim cmt As Comment
Dim StartCell As Range
Set Sht = Worksheets("Sheet06")
For Each cmt In Sht.Comments
If cmt.Text = "$StartCell" Then
Set StartCell = cmt.Parent
Debug.Print StartCell.Address
End If
Next cmt
End Sub

error 91 object variable or with block variable not set on range

I'm new to VBA as was trying to write a macro that finds duplicates in different columns from a worksheet. I found the answer here helpful. However this solved it only for one column. So to add a few more columns I altered the code as follows
Sub test()
Dim iWarnColor As Integer
Dim rng As Range
Dim rngCell As Variant
Dim iWarnColor1 As Integer
Dim rnga As Range
Dim rngCell1 As Variant
Set rng = Range("A1:A17") ' area to check '
iWarnColor = xlThemeColorAccent2
For Each rngCell In rng.Cells
vVal = rngCell.Text
If (WorksheetFunction.CountIf(rng, vVal) = 1) Then
rngCell.Interior.Pattern = xlNone
Else
rngCell.Interior.ColorIndex = iWarnColor
End If
Next rngCell
Set rnga = Range("B1:B17") ' area to check '
iWarnColor1 = xlThemeColorAccent3
For Each rngCell1 In rnga.Cells
vVal = rngCell1.Text
If (WorksheetFunction.CountIf(rnga, vVal) = 1) Then
rngCell1.Interior.Pattern = xlNone
Else
rngCell1.Interior.ColorIndex = iWarnColor1
End If
Next rngCell1
End Sub
On running this code I get
"Run time error 91: object variable or with block variable not set "
It says the error is at
For Each rngCell1 In rnga.Cells
What am I doing wrong here??
I can run your code locally in Excel 2010 without any errors, so not sure exactly what the problem is.
Try to change the type of rngCell1 from Variant to Range and see if it makes any difference.
As a side note, vVal has not been Dimed. It will only complain if you add Option Explicit at the top of your module, so it shouldn't matter here.

Copy a range with rows to another sheet

My requirement is to copy rows from sheet3 having font color black to sheet1.I have a range of rows selected from sheet3 in a workbook. I want to copy this and paste in sheet1.Selection part is ok, but Error (Application defined or object defined ) in copy statement.
Sub Copy()
Dim lastRow, i As Long
Dim CopyRange As Range
lastRow = Sheet3.Rows.Count
With Sheets(Sheet3.Name)
lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 1 To lastRow
If .Rows(i).Font.Color = 0 Then
If CopyRange Is Nothing Then
Set CopyRange = .Rows(i)
Else
Set CopyRange = Union(CopyRange, .Rows(i))
End If
End If
Next
End With
CopyRnge.Copy Destination:=Worksheets("Sheet1").Range("A1:J300")
End Sub
Option Explicit forces you to declare ALL the variables you use.
CopyRnge.Copy didn't exist when you ran the program, so Excel showed a run-time error.
Common errors like these can be avoided by turning on Option Explicit by default.
How to enable Option Explicit for all modules in VBA:
Suggested Code To Try:
The code below uses Option Explicit and it also takes advantages of using Object References.
By setting up Object References, you can rely on Intellisense to ensure you avoid typos.
Option Explicit
Sub CopyBlackText()
Dim lastRow As Long
Dim i As Long
Dim srcRangeToCopy As Range
Dim destinationRange As Range
Dim wrkbook As Workbook
'Setup Object references by assigning and using the 'Set' keyword
Set wrkbook = ActiveWorkbook
Set destinationRange = wrkbook.Worksheets("Sheet1").Range("A1:J300")
With wrkbook.Worksheets("Sheet3")
'Using Cells(1,1).Address instead of saying Range("A1")
lastRow = .Range(Cells(1, 1).Address).End(xlDown).Row
For i = 1 To lastRow
If .Rows(i).Font.Color = 0 Then
If srcRangeToCopy Is Nothing Then
Set srcRangeToCopy = .Rows(i)
Else
Set srcRangeToCopy = Union(srcRangeToCopy, .Rows(i))
End If
End If
Next
End With
srcRangeToCopy.Copy Destination:=destinationRange
End Sub