Setting variables VBA - vba

complete novice here
I started some VBA a few days ago, I have simple question but cant seem to find what I am doing wrong.
I am trying to make a button which will take the coordinates of the active cell and compare them to another worksheet to retrieve a specific value from another table.
I set variables to the active cell column and row, I want to do this so I can later compare these locations to another worksheet and get the value at a specified position on another worksheet.
So far I have written simply what I could find on the internet as I have no formal training.
The msgbox at the end is just to test whether or not it actually picks up the reference.
Sub CommandButton1_Click()
Dim Arow As Range
Dim Acol As Range
Set Arow = Worksheets("Sheet1").Range(ActiveCell.Row)
Set Acol = Worksheets("Sheet1").Range(ActiveCell.Column)
MsgBox (Arow)
End Sub
So far I have error run-time error '1004' Application defined or object defined error highlighting the 4th Row. If anyone could help me solve this or redirect me to some help it would be much appreciated.

I think this won't work, you should put there
Set arow = Worksheets("Sheet1").Range(ActiveCell.Row & ":" & ActiveCell.Row)
Putting there simply number won't work. For the column, you should put there somethong like C:C. For getting letter of column, see this qestion: Function to convert column number to letter?
For more information about Range property, please see official documentation https://msdn.microsoft.com/en-us/library/office/ff836512.aspx.
The thing is, that you have to supply either the address in so called A1 reference, which is "A1", or "$A$1" or name of cell, etc, or you have to supply two Range objects, such as two cells Worksheets("Sheet1").Range(Worksheets("Sheet1").Cells(1,1), Worksheets("Sheet1").Cells(2,2)), which defines area starting with upper-left corner in first parameter and lower right in second parameter.

ActiveCell.Row and ActiveCell.Column returns you some Integer value representing number of row and column, i.e. if you point cell B4, ActiveCell.Row would return 4, and ActiveCell.Column gonna return 2. An Range() property need as an argument whole adress for some range, i.e. Range("C6") or Range("G3:J8").
When you have your column as a number, you can use Cells() property for pointing first and last cell in your range, i.e. Range(Cells(2, 4), Cells(6, 8) would be the same range as Range("D2:H6").
Following this, one of the ways that you can do what you have described is:
Sub CommandButton1_Click()
Dim Rng As Range
Set Rng = Worksheets("Sheet1").Cells(ActiveCell.Row, ActiveCell.Column)
End Sub
Now you have under variable Rng an Range of the same coordinates as ActiveCell, but in Sheet1. You can pass some value into i.e Rng.Value = "Hello World", paste something with Rng.PasteSpecial xlPasteAll etc.

if you want the value from other sheet at the same location as activeCell, use this code,
Private Sub CommandButton1_Click()
valueFromOtherSheet = Sheets("Sheet2").Range(ActiveCell.Address)
MsgBox (valueFromOtherSheet)
End Sub

Like the others have said, it's just about knowing your variable types. This is another way you could achieve what you want
Sub CommandButton1_Click()
Dim Acell As Range
Set Acell = Worksheets("Sheet2").Range(ActiveCell.Address)
MsgBox "Value on ActiveSheet: " & ActiveCell.Value & vbNewLine & _
"Value on Sheet2: " & Acell.Value
End Sub

Thank you everyone for the help and clarification, In the end I was able to come up with some code that seems to do what I need it to.
Private Sub CommandButton1_Click()
Dim cabDate As Range
Dim searchCol As Integer
Dim newindex As Range
Set cabDate = WorksheetFunction.Index(Range("A1:O9999"), ActiveCell.Row, 2)
searchCol = ActiveCell.Column
Set newindex = WorksheetFunction.Index(Worksheets("Deadlines").Range("A1:O9999"), cabDate.Row, searchCol)
MsgBox (newindex)
End Sub
I wasn't aware about conflicting data types so thank you all for the assistance.

Related

Replace cell values in specific sheets with defined name

I am trying to run some code that replaces the cell values in a specific column with a defined name. In addition, I have a condition that the replacement should only take place if the first 9 characters of the values are xxxxxxxxx.
More precisely, it should change the values in C:C in 2 specific worksheets (I don't want to loop through the whole workbook).
I am not sure why nothing happens in the code (no error messages, nothing).
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets. I am also aware that my use of Range is probably not totally correct.
Sub ChangeMe()
Dim cl As Range
For Each cl In Worksheets("Sheet1").Range("C:C").End(xlUp)
With Worksheets("Sheet2").Range("C:C").End(xlUp)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
End With
Next cl
End Sub
In answer your original questions:
I am not sure why nothing happens in the code (no error messages, nothing).
Nothing happens because your worksheet values are lowercase xxxxxxxxx, whilst your code checks for uppercase XXXXXXXXX.
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets.
Actually, you can use With with multiple sheets, as I will demonstrate below.
I am also aware that my use of Range is probably not totally correct.
That is true. If you were to fix the uppercase issue, only C1 would be changed. This is because .End() works on a single cell. If you supply a multi-cell range, it uses the top left most cell. So .Range("C:C").End(xlUp) is equivalent to .Range("C1").End(xlUp) which evaluates to just C1.
The following will answer your updated question:
Option Explicit
Public Sub ChangeMe()
Const l_xxxxxxxxx As String = "xxxxxxxxx"
Const l_MyDefinedName As String = "MyDefinedName"
Const s_Delimiter As String = ","
Const s_WorkSheetNames As String = "Sheet1,Sheet2"
Const s_ColumnToChange As String = "C:C"
Dim varWorkSheetName As Variant
For Each varWorkSheetName In Split(s_WorkSheetNames, s_Delimiter)
With Worksheets(varWorkSheetName).Range(s_ColumnToChange)
Dim rngCell As Range
For Each rngCell In .Resize(.Cells(Rows.Count).End(xlUp).Row)
With rngCell
Dim strCellValue As String: strCellValue = .Value2
If Left(strCellValue, Len(l_xxxxxxxxx)) = l_xxxxxxxxx Then
.Value2 _
= Names(l_MyDefinedName).RefersToRange.Value2 _
& Right$(strCellValue, Len(strCellValue) - Len(l_xxxxxxxxx))
End If
End With
Next rngCell
End With
Next varWorkSheetName
End Sub
Notes:
It is a good idea to use constants so all literal values are typed once only and kept grouped together.
Using .Value2, instead of .Value, is the recommended way to access a cell's value as it avoids implicit casting and is therefore faster. (Using .Value can also sometimes cause issues.)
Surprisingly, in VBA there are good reasons to put a variable declaration as close as possible to the first use of the variable. Two such reasons are 1) it improves readability, and 2) it simplifies future refactoring. Just remember that the variable is not reinitialised every time the Dim is encountered. Initialisation only occurs the first time.
If I understood your post correctly (which I doubt it), I think you want to loop through column "C" in both "Sheet1" and "Sheet2". Every cell that starts with 9 "XXXXXXXXX", should be replaced with the value in "MyDefinedName" Named Range.
Code
Option Explicit
Sub ChangeMe()
Dim cl As Range
Dim sht As Worksheet
For Each sht In ThisWorkbook.Sheets
With sht
If .Name = "Sheet1" Or .Name = "Sheet2" Then
For Each cl In .Range("C1:C" & .Cells(.rows.Count, "C").End(xlUp).Row)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
Next cl
End If
End With
Next sht
End Sub
Let's imagine that this is your input:
In this case, you want to change the values in range A1:A2 to the value in C1 (named range xxxx123), because it starts with xxxx123. This is the code to achieve it:
Public Sub TestMe()
Dim myCell As Range
Dim myNamedRange As String
myNamedRange = "xxxx123"
For Each myCell In Range("A1:A2")
If Left(myCell, Len(myNamedRange)) = myNamedRange Then
myCell.Value = Range(myNamedRange)
End If
Next myCell
End Sub

Strange cell addresses behaviour for non-contiguous ranges: VBA

I was trying to answer this question when I came across some bizarre VBA behaviour in Excel. I have written a very simple sub to demonstrate the issue:
Sub debugAddresses(rng As Range)
Debug.Print "Whole range: " & rng.Address
Dim i As Long
For i = 1 To rng.Cells.Count
Debug.Print rng.Cells(i).Address
Next i
End Sub
I loop over each cell in a range object and print its address, simple right?
debugAddresses Range("B2:B3")
' Result as expected:
' >> Whole range: $B$2:$B$3
' >> $B$2
' >> $B$3
However, for non-contiguous ranges I get some strange behaviour:
debugAddresses Range("A1,B2")
' Strange behaviour when getting addresses of individual cells:
' >> Whole range: $A$1,$B$2
' >> $A$1
' >> $A$2
Can anyone shed any light on this please? Specifically why the Cells objects, which can be used for indexing of a contiguous range, seem to just extend the first selected Area.
Edit: It might be worth noting that using a For Each loop through the actual cell range objects gives the expected result*
Sub debugAddresses2(rng As Range)
Debug.Print "Whole range: " & rng.Address
Dim c As Range
For Each c In rng
Debug.Print c.Address
Next c
End Sub
*See my answer for a comment on a more robust solution, as this (apparently) may not always give the expected result
Try using the modified Sub debugAddresses code below:
Sub debugAddresses(rng As Range)
Dim RngA As Range
Dim C As Range
For Each RngA In rng.Areas
For Each C In RngA.Cells
Debug.Print C.Address
Next C
Next RngA
End Sub
Here is your code "fixed". by just adding one more For Loop
Sub debugAddresses(rng As Range)
Debug.Print "Whole range: " & rng.Address
For Each r In rng ' this loops through the range even if separated cells
Dim i As Long
For i = 1 To r.Cells.Count 'changed to r instead of rng
Debug.Print r.Cells(i).Address 'changed to r instead of rng
Next i
Next r
End Sub
So .Range works by entering the cell address ex. "B1", or by using R1C1 meaning row column ex. 1,2.
But you cant use just one R1C1 inside of .Range, since the range here is a span of cells. So to properly use R1C1 in .Range, you have to specify 2 of them.
So .Range("B5:B10") is equal to Range(Cells(5,2),Cells(10,2))
What you did was Specify a Range, then from that created another range using Cells. Very much like offset.
So Range("A1,B2") then adding Cells(1) then Cells(2) adds rows to the first range that is "A1" or offsets.
Sub selector()
Set Rng = Range("A1")
Rng.Select
Rng.Cells(4, 4).Select
End Sub
This offsets 4 colums and 4 rows from A1
It appears that Florent's comment was in the correct direction, and that this method is extending the first Area within the range object.
In a contiguous range (e.g. "A1:B5", "C10:C100") the following method loops over each cell in the given range, rng.
Dim j As Long
For j = 1 To rng.Cells.Count
Debug.Print rng.Cells(j).Address
Next j
However, in non-contiguous ranges it appears that this is equivalent (or shorthand for)
For j = 1 To rng.Cells.Count
Debug.Print rng.Areas(1).Cells(j).Address
Next j
There doesn't appear to be any direct mention of this in the documentation but it is a sensible conclusion to draw by looking in the Locals browser of the VBA editor.
In the range object rng, there is a Cells property which only contains one "Item", which is the first Area. So it's reasonable to assume this one item is what .Cells(j) has access to.
In rng we can also see the Areas property, which contains 2 items (in this example) equal to the number of Areas in my non-contiguous range.
So rng.Cells(j) is accessing the jth element within the first area of rng. Because .Cells() can extend past the original size of rng, we see the addresses listed of cells outside rng.
The solution(s):
Either ensure you directly loop through the range objects within rng using a For Each loop as shown in the question.
Or loop over each area, and then each cell within that area.
The first option is more concise, but Shai points out that to be completely sure, the most robust method is to do the two For Each loops as there may be more complicated edge cases which aren't captured with the single loop.

Substitute Excel Formula Argument (provided by the user) with a Variable

Concept:
Entire Rows are deleted through a macro based off parameters which are represented as an excel formula by the user. The idea here is that a user can use Boolean formulas that they're already familiar with to evaluate values in a range (read the "Process" below for further clarification).
Process:
A user clicks on a button which shows a form. This form contains two input fields (or parameters); "Column" and "Formula". The "Column" is the range for which the macro will be cycling through (let's say $A:$A). The "Formula" is an Excel based formula represented as such, in the user parameter field ie =OR(A1="X",A1="Y"). However, I've instructed the user to replace any instance of A1 with rng. I've requested the user to do this because the idea here is that I would replace rng with a changing variable in VBA that cycles through all the cells specified in the "Column" parameter.
Problem:
I'm not aware of any way to replace the rng representation within the Excel formula with a range variable in VBA.
Update 4-7-17
Thank you all for your responses but I'm pretty certain my problem is getting lost in translation. I'm aware this is my fault, since I didn't provide any code for analysis. Unfortunately, therein lies the issue. I don't know what to write. I'm going to do my best to write some code (that I know is wrong) which will hopefully convey what I'm trying to accomplish.
Sample Code 4-7-17
Sub SampleCode()
Dim wRng As Range
Set wRng = Range("A1:A26") 'Let's assume that the values in this range are the
' letters of the alphabet
Dim Counter As Integer
'Cell "B2" will contain a formula that the user has entered
'which is: =OR(rng="X",rng="M")
'Obviously the formula returns an error in excel (#NAME? to be
'exact) but that's understood.
Dim wFormula As String
wFormula = Range("B2").Formula
Dim rng As Range 'This variable "rng" is what is represented in the
'formula that was written in Range("B2")
'*** This is where I get stuck. I know I'm missing code here to
'be able to proceed with my routine below.
'code
'code
'code
Counter = wRng.Rows.Count
For i = 1 To wRng.Rows.Count
Set rng = Cells(Counter, 1)
If wFormula = True Then
rng.EntireRow.Delete
End If
Counter = Counter - 1
Next i
'The ending result should be that row 24 was deleted because it contained
'the letter "X" and row 13 was deleted because it contained the letter "M"
'
'The objective of this code is to use any Excel formula which evaluates out
'to a True or False value.
End Sub
Hey Jon first you need to declare a Variable of relevant data type, then pass value from Range & finally use where you wish to, like,
Dim Src As Variant
Src= Sheets( "Sheet3" ).Range( "A2:A9" ).Value
Hey John this code will help you to get the solution,
Public Sub ProcessData()
Const TEST_COLUMN As String = "A"
Dim Lastrow As Long Dim i As Long
Application.ScreenUpdating = False
With ActiveSheet
Lastrow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = Lastrow To 1 Step -1
If Cells(i, TEST_COLUMN).Value2 Like "AU" Or _ Cells(i, TEST_COLUMN).Value2 Like "AZ" Then
.Rows(i).Delete
End If
Next i
End With
Application.ScreenUpdating = True
End Sub

VBA search and copy

I'm automating an update I have to do and part of the macro I want to write needs specific text from what gets populated.
I have the following types of text in the same column for hundreds of rows:
ScreenRecording^naushi02^procr^10035
procr^10635^ScreenRecording^misby01
ScreenRecording^liw03^procr^10046
I've bold the text I need. I want to either replace the whole text with just what I need or place what I need in the next column, same row.
I had wrote something which worked for 60 or so lines before I realised that there are variations in the format. For the main, it's all the same which is why I didn't realise at first and I've spent a lot of wasted time writing something that is now useless... so I'm asking for expert help please.
Once I've got what I need from the first row, I need to move down until the last entry repeating.
I had some code which obviously didn't work fully.
I have thought about using the text 'ScreenRecording' in a search along with the special character which I can't find on my keyboard and then trying to copy all text from that point upto and including the 2nd numerical character. I don't know how to do this, if it would work or even if it's a good idea but because I've spent so much time trying to figure it out, I need some help please.
Thanks in advance
If you always want to return the value after the word 'ScreenRecording`, you can use the following function to do so.
Include it in a SubRoutine to replace in place if needed:
Function SplitScreenRecording(sInput As String) As String
Dim a As Variant
Const SDELIM As String = "^"
Const LOOKUP_VAL As String = "ScreenRecording"
a = Split(sInput, SDELIM)
If IsError(Application.Match(LOOKUP_VAL, a, 0)) Then
SplitScreenRecording = CVErr(2042)
Else
SplitScreenRecording = a(Application.Match(LOOKUP_VAL, a, 0))
End If
End Function
Sub ReplaceInPlace()
Dim rReplace As Range
Dim rng As Range
Set rReplace = Range("A1:A3")
For Each rng In rReplace
rng.Value = SplitScreenRecording(rng.Value)
Next rng
End Sub
if you want to replace:
Sub main2()
Dim key As String
Dim replacementStrng As String
key = "ScreenRecording"
replacementStrng = "AAA"
With Worksheets("mysheet01").columns("A") '<--| change "mysheet01" and "A" to your actual sheet name and column to filter
.Replace what:=key & "^*^", replacement:=key & "^" & replacementStrng & " ^ ", LookAt:=xlPart
.Replace what:="^" & key & "^*", replacement:="^" & key & "^" & replacementStrng, LookAt:=xlPart
End With
End Sub
while if you want to place what you need in the next column:
Sub main()
Dim myRng As Range
Set myRng = GetRange(Worksheets("mysheet01").columns("A"), "ScreenRecording^") '<--| change "mysheet01" and "A" to your actual sheet name and column to filter
myRng.Offset(, 1) = "value that I need to place in next row" '<--| change the right part of the assignment to what you need
End Sub
Function GetRange(rng As Range, key As String) As Range
With rng
.AutoFilter Field:=1, Criteria1:="*" & key & "*" '<--| apply current filtering
If Application.WorksheetFunction.Subtotal(103, .Cells) > 0 Then '<--| if there are visible cells other than the "header" one
With .SpecialCells(xlCellTypeConstants)
If InStr(.SpecialCells(xlCellTypeVisible).Cells(1, 1), key & "^") > 0 Then
Set GetRange = .SpecialCells(xlCellTypeVisible) '<--|select all visible cells
Else
Set GetRange = .Resize(.Parent.Cells(.Parent.Rows.Count, .Column).End(xlUp).row - 1).Offset(1).SpecialCells(xlCellTypeVisible) '<--|select visible rows other than the first ("headers") one
End If
End With
End If
.Parent.AutoFilterMode = False '<--| remove drop-down arrows
End With
End Function

Select Method of Worksheet Class Failed

I have this sub in Excel 2010 which is supposed to filter through all the cells in a sheet until it finds a match to Proj No, then paste a field from this row into another field.
When I try to run the sub, it gives me an error 1004: Select Method of Worksheet Class Failed. I've marked the line where this occurs. Any assistance would be greatly appreciated.
Option Explicit
Private Sub btnNext_Click()
Dim ProjNo As String
Dim Col As String
Dim Row As String
Dim cell As Range
Unload Dialog
formWait.Show
Sheets("Sheet7").Activate
ProjNo = Worksheets("Sheet1").Range("D6").Value
Col = Cells(Rows.Count, "A").End(xlUp).Row
For Each cell In Range("A2:A" & Col) 
If cell.Value = ProjNo Then
Row = Row & cell.Row
End If
Next cell
Workbooks("Form.xlsm").Sheets("Sheet7").Range("Row, 6").Copy Destination:=Sheets("Sheet1").Range("19, 5") ‘Error
Unload formWait
End Sub
I don't know what GWP is, but I think you want to use ProjNo there. The Range property doesn't accept an argument like that. Unless you have a named range of "Row,6" which you don't because it's not a legal name, you have to supply Range with a valid range reference, like A6 or D2:D12, for example.
Also, you can't concatenate rows and use them in a Range reference to get a larger range. You would have to copy each row inside the loop, union the ranges as you go, or better yet, filter on the value that you want and copy the visible rows.
Try this:
Private Sub btnNext_Click()
With ThisWorkbook.Worksheets("Sheet7")
'filter for the project id
.Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).Resize(, 6).AutoFilter 1, "=" & .Range("D6").Value
'copy the visible rows
.Range("F2", .Cells(.Rows.Count, 6).End(xlUp)).SpecialCells(xlCellTypeVisible).Copy _
ThisWorkbook.Worksheets("Sheet1").Cells(19, 5)
'get rid of the filter
.AutoFilterMode = False
End With
End Sub
There are a few confusing items in your code above, so I wanted to place them long-form here. Let's get started:
Dim Col As String
Dim Row As String
It looks like your design expects these to be of type Long rather than type String. Even if these variables were meant to be strings, I would recommend adjusting their names -- when your fellow developer attempts to review your design, he or she is likely to see names like "Col" or "Row" and think "these are numbers". Easy fix:
Dim Col As Long, Row As Long
The next issue comes up here:
Col = Cells(Rows.Count, "A").End(xlUp).Row
The structure above is a common method for identifying the last ROW, not column. (It also appears that you have switched the "A" and number, which is another easy fix). While it is perfectly acceptable syntactically to name the variable for last row "Col", human users are likely to find this confusing. Identifying the last row (and the last col, which you use in the For Each loop), as explained in fantastic detail here, would be better handled like this:
Dim SheetSeven As Worksheet, SheetOne As Worksheet
Dim LastRow As Long, LastCol As Long
Set SheetSeven = ThisWorkbook.Worksheets("Sheet7")
Set SheetOne = ThisWorkbook.Worksheets("Sheet1")
With SheetSeven
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
LastCol = .Range("A" & .Columns.Count).End(xlToLeft).Column
End With
This should make your For Each loop look like this:
With SheetSeven
For Each cell in .Range("A2:A" & LastCol)
'... do you comparison and row incrementing here
Next cell
End With
Once you've identified your sheet as a variable, the Range.Copy action should be much easier as well:
With SheetSeven
.Range(.Cells(Row, 6)).Copy _
Destination:=SheetOne.Range(SheetOne.Cells(19, 5))
End With
Also one other thing you may wish to check is the status of Application.ScreenUpdating.
With the release of Office 2013 and later, a SDI (Single Document Interface) was introduced. If Application.ScreenUpdating is False and the workbook is not active, the implied call to Workbook.Activate will fail. Check the status of ScreenUpdating and set it to True if needed. You can set it back to False after the first Activate call for that workbook is made.
See this article:
https://support.microsoft.com/en-us/help/3083825/excel-workbook-is-not-activated-when-you-run-a-macro-that-calls-the-wo
In my case the error came as the sheet was hidden.
so I check if I am not working with the hidden sheet. Or you need to unhide the sheet before you try to select or activate sheet.
For Each sh In ThisWorkbook.Sheets
If Left(sh.Name, 8) <> "Template" Then
sh.Select
sh.Range("A1").Select
End If
Next