Excel - Writing Macros that will write on the next line - vba

I apologize in advance if my question is too simplistic but I'm rather new to coding.
I'm trying to gather an information from a table and have the code list it out on separate lines.
The problem is that I'm trying to figure out a function that write on the cell or cells below the cell with the function.
For example, if I enter my function gatherinfo(x,y,z) into A1, I want the output to be written on A1, A2, and A3 (assuming that it has three different outputs).
I've been trying to look around for a function/code that represents the cell that the formula is put into but I'm having a tough time looking for it. I know that Activecell function works when I click on the cell and then run the macro. But I want a function that will just work off of the cell that contains that function (without depending on the active cell).
I hope that I'm making some sense. Thanks all in advance for your help!!

Place the following code inside the worksheet code object :
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count = 1 Then
If Target.Formula Like "=gatherinfo(*,*,*)" Then
MsgBox "The formula " & Target.Formula & " was added to the cell " & Target.Address & "." & vbCrLf & vbCrLf & "Do something with it!"
End If
End If
End Sub
Worksheet code object :
You can then play around with the content of your worksheet and whenever you enter the function "gatherinfo" with 3 parameters, VBA will handle what to do. To know the row and column of the "Target" cell, you can use properties such as .Row and .Column.

Related

Hyperlink not working correctly [duplicate]

This question already has an answer here:
Macro to Hyperlink a cell to itself
(1 answer)
Closed 4 years ago.
I have two hyperlinks in my excel document.
On Sheet1, in cell A1 I have written the formula, =HYPERLINK("#'Sheet1'!A1","click").
On Sheet1, in cell A2 I have done what I think is the same but using a interactive GUI. Right click in cell A2 => Hyperlink => Place in this document => (Type the cell reference) A2.
I also have written a very short sub so that when I click on these hyperlinks I get a message box.
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
MsgBox ActiveCell
End Sub
I have placed this macro in Sheet1.
When I click on cell A2 I get a MsgBox with the cell value in. As expected
However, when I click on cell A1 nothing happens.
Why are these two links behaving differently? How can I make cell A1 behave in the same way as cell A2 whilst still using a formula in the cell?
So Why?
I have a program that generates a CSV file. For simplicity the structure looks a little like this.
f1,f2,compare
f3,f2,compare
This means it is easy to overwrite the format.
I then open this file and save it as a .xlsm. What I wanted to have is when the compare is clicked it would run a macro. This could be in the form of a button or a hyperlink or anything else as long as it is obviously clickable.
Hence why I was going for the approach of =HYPERLINK("#'Sheet1'!A1","click") as it was easy to increment the number after the column and still show it was something that was clearly clickable.
Therefore it would be a quick change to get the CSV file output in a format of
f1,f2,"=HYPERLINK(""#'Sheet1'!C1"",""compare"")"
f3,f2,"=HYPERLINK(""#'Sheet1'!C2"",""compare"")"
The goal of this is to have something that is as automated as possible as this workbook could have over a thousand row so it is not feasible for me to manually sort out a compare button on each row
Update 2
When the compare button is clicked, a macro is run. This macro is called CompareFiles. It takes the values from the cells on the same row but in columns a and b, passes them into a shell command, and opens a different program that is used to compare the files.
What you can do is select all the cells that need linking to themselves and use a answer by Gary's Student to do it. The original post can be found here
Sub HyperAdder()
Dim r As Range, s As String
For Each r In Selection
If Len(r.Text) = 0 Then
s = "X"
Else
s = r.Text
End If
ActiveSheet.Hyperlinks.Add Anchor:=r, Address:="", SubAddress:=r.Parent.Name & "!" & r.Address(0, 0), TextToDisplay:=s
Next r
End Sub
If you are using a .vbs file like myself, in my case to convert the .csv and do a few other things, then you can use something similar to the following.
For Each cell In YourSheet.UsedRange.Columns("C:F").Cells
If Len(cell.Text) > 0 Then
ConflictsSheet.Hyperlinks.Add cell, "", "'" & cell.Parent.Name & "'" & "!" & cell.Address(0, 0), cell.Text
End If
Next

Excel vba if the statements and auto update

im looking for some basic help with VBA. I have created a userform for fellow members to fill out information when we go on a call. When ok is pressed, the data is sent to sheet2 and laid in the next open row. In sheet 3 i have the printable form of the userform that i made out. I am trying to write the vba code in the printable form, so when the call # changes in cell b2, it will automatically change all other cells in that form, with the corresponding data in the same row from sheet 2.
Im new to the forum so im not sure how to upload etc. but i will try and give an example below.
Sheet2 looks something like this, for the example each cell seperated by "."
18-170001.01/02/17.Accident."current address"."call info"
18-170002.01/02/17.Training."current address"."call info"
in sheet4 this is the code i am using and it is not working
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Long
For i = 5 To Row.Count
If sheet4 .cells(b2).value=sheet2.Cells(i,1).value then
sheet4.cells(e2).value=sheet2.cells(i,1).value
End If
Next i
End Sub
I would add additional if then statments for each cell in sheet 4 to match each cell in sheet 2.
Please any help is appreciated
You can't use Cells(b2), it needs to be Range("B2") or Cells(2, "B") or Cells(2, 2).
So your code could be:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Long
For i = 5 To Row.Count
If sheet4.cells(2, "B").value=sheet2.Cells(i,1).value then
sheet4.cells(2, "E").value=sheet2.cells(i,1).value
End If
Next i
End Sub
But you would be better off just using a formula for this purpose instead. For example, the above code could be replaced by having the following formula in cell E2 of sheet4:
=VLOOKUP(B2,Sheet2!A:A,1,FALSE)
(That assumes that sheet2 has a sheet name of "Sheet2".)

Using Vlookup to copy and paste data into a separate worksheet using VBA

Alright I'm a beginner with VBA so I need some help. Assuming this is very basic, but here are the steps I am looking at for the code:
-Use Vlookup to find the value "Rec" in column C of Sheet1, and select that row's corresponding value in column D
-Then copy that value from column D in Sheet1 and paste it into the first blank cell in column B of another worksheet titled Sheet2
I've got a basic code that uses Vlookup to find Rec as well as it's corresponding value in column D, then display a msg. The code works fine, and is the following:
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
MsgBox "GSV is :$" & total_gsv
End Sub
I also have another one that will find the next blank cell in column B Sheet2, it works as well:
Sub SelectFirstBlankCell()
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Select: Exit For
Next cell
End Sub
Not sure how to integrate the two, and I'm not sure how to make the code paste the Vlookup result in Sheet2. Any help would be greatly appreciated, thanks!
So for being a beginner you're off to a good start by designing two separate subroutines that you can confirm work and then integrating. That's the basic approach that will save you headache after headache when things get more complicated. So to answer your direct question on how to integrate the two, I'd recommend doing something like this
Sub BasicFindGSV()
Dim movement_type_code As Variant
Dim total_gsv As Variant
movement_type_code = "Rec"
total_gsv = Application.WorksheetFunction.VLookup(movement_type_code, Sheet1.Range("C2:H25"), 2, False)
AssignValueToBlankCell (total_gsv)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Dim Sheet2 As Worksheet
Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) = True Then cell.Value2 = v
Next cell
End Sub
That being said, as Macro Man points out, you can knock out the exact same functionality your asking for with a one liner. Keeping the operational steps separate (so actually a two liner now) would look like this.
Sub FindGSV()
AssignValueToBlankCell WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
Sub AssignValueToBlankCell(ByVal v As Variant)
Sheet3.Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value2 = v
End Sub
Like I said, if you plan to continue development with this, it's usually a good idea to design your code with independent operations the way you already have begun to. You can build off of this by passing worksheets, ranges, columns, or other useful parameters as arguments to a predefined task or subroutine.
Also, notice that I use Value2 instead of Value. I notice you're retrieving a currency value, so there's actually a small difference between the two. Value2 gives you the more accurate number behind a currency formatted value (although probably unnecessary) and is also faster (although probably negligible in this case). Just something to be aware of though.
Also, I noticed your use of worksheet objects kind of strange, so I thought it'd help to mentioned that you can select a worksheet object by it's object name, it's name property (with sheets() or worksheets()), index number (with sheets() or worksheets()), or the "Active" prefix. It's important to note that what you're doing in your one subroutine is reassigning the reference of the Sheet2 object to your active sheet, which means it may end up being any sheet. This demonstrates the issue:
Sub SheetSelectDemo()
Dim Sheet2 As Worksheet
Set Sheet2 = Sheets(1)
MsgBox "The sheet object named Sheet2 has a name property equal to " & Worksheets(Sheet2.Name).Name & " and has an index of " & Worksheets(Sheet2.Index).Index & "."
End Sub
You can view and change the name of a sheet object, as well as it's name property (which is different) here...
The name property is what you see and change in the worksheet tab in Excel, but once again this is not the same as the object name. You can also change these things programmatically.
Try this:
Sub MacroMan()
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Value = _
WorksheetFunction.VLookup("Rec", Sheet1.Range("C2:H25"), 2, False)
End Sub
The Range("B" & Rows.Count).End(xlUp) command is the equivalent of going to the last cell in column B and pressing Ctrl + ↑
We then use .Offset(1, 0) to get the cell after this (the next blank one) and write the value of your vlookup directly into this cell.
If Both work, then good, you have two working subs and you want to integrate them. You probably want to keep them so they might be useful for some other work later. Integrating them means invoking them in some third routine.
For many reasons, it is surely better and advised to avoid as much as possible to use (select, copy, paste) in VBA, and to use rather a direct copying method (range1.copy range2).
You need to make your routines as functions that return ranges objects, then in some third routine, invoke them
Function total_gsv() as range
Dim movement_type_code As Variant: movement_type_code = "Rec"
Set total_gsv = Application.WorksheetFunction.VLookup(movement_type_code,Sheet1.Range("C2:H25"), 2, False)
End Sub
Function FindFirstBlankCell() as Range
Dim Sheet2 As Worksheet: Set Sheet2 = ActiveSheet
For Each cell In Sheet2.Columns(2).Cells
If IsEmpty(cell) Then Set FindFirstBlankCell= cell: exit For
Next cell
End Sub
Sub FindAndMoveGsv()
total_gsv.copy FindFirstBlankCell
... 'some other work
End Sub

VBA to create formula causes 1004 error when parentheses entered and causes an update values box when space is entered

I have a UserForm with TextBoxes that are used in creating new sheets from a template.
The gist of how this all works is that the VBA creates a copy of a "template sheet" that is hidden in the workbook. It names this copy based on the values entered by the user in the form's TextBox.
Additionally, I have a Summary sheet that is, as the name implies, a summary of different information entered in the sheet(s) the user has created.
To relieve the need for users to manually copy info into the Summary sheet, I'm using a formula tied to a function which will update the Summary sheet for them.
Now, when the user finishes entering their information into the UserForm, they click a Generate Workbook button which runs the code below which build formulas into the Summary sheet based on the info entered in the UserForm TextBox.
I have two issues when the VBA attempts to add a formula to the cells in the Summary Sheet.
Issue 1:
When a space is entered into the TextBox, it opens a "Update Values" dialog box when the user clicks the Generate Workbook button.
Example - Project 1234
Issue 2:
When parentheses are entered into the TextBox, a 1004 error occurs when the user clicks the Generate Workbook button.
Example - Project1234(Mobile)
If no parentheses and no spaces are entered, it all works fine.
There are 25 possible entries in the UserForm.
Each entry has three TextBoxes: ProjectNameTXT, SheetNameTXT, and ProjectNumTXT - each numbered 1-25 (i.e., ProjectNameTXT1, SheetNameTXT1, ProjectNumTXT1).
The two issues only pertain to the SheetNameTXT TextBoxes.
The csvRange is the function I mentioned earlier.
Here's the relevant code.
Code in UserForm1:
Private Sub GenerateWorkbook_Click()
Dim ws As Worksheet
Dim k As Long
Dim strSName
For k = 1 To 25
strSName = Me.Controls("SheetNameTXT" & k).Text
'Creates a data sheet for each project.
'Uses the MASTER SHEET as a template.
Set ws = ThisWorkbook.Worksheets("MASTER SHEET")
ws.Copy ThisWorkbook.Sheets(Sheets.count)
ActiveSheet.Name = strSName
ActiveSheet.Visible = xlSheetHidden
ThisWorkbook.Worksheets("Summary").Select
'THIS IS THE CODE CAUSING THE ISSUES
Range("B" & k + 3).Value = "=IF(ISERROR(csvRange(" & strSName & "!A2:A2500)),"""",csvRange(" & strSName & "!A2:A2500))"
Next k
Unload UserForm1
End Sub
Code for the Function csvRange:
Function csvRange(myRange As Range)
Dim csvRangeOutput
For Each entry In myRange
If Not IsEmpty(entry.Value) Then
'Create comma separated value
csvRangeOutput = csvRangeOutput & entry.Value & ", "
End If
Next
'Removes the last comma and space.
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 2)
End Function
The csvRange function is a modified version of this function that muncherelli created:
https://superuser.com/a/241233
I'm not the world's greatest VBA coder, so apologies if my syntax or methodology sucks. Feel free to improve and provide suggestions if you are so inclined.
I searched StackOverflow and didn't find anything that would solve the problem. Tried some of the solutions suggested for similar problems but no luck getting them to fix these issues.
As always, your help and constructive criticism are much appreciated.
Put single quotes around your sheet name in your formula.
"=IF(ISERROR(csvRange('" & strSName & "'!A2:A2500)),"""",csvRange('" & strSName & "'!A2:A2500))

Get the cell reference of the value found by Excel INDEX function

The Problem
Assume that the active cell contains a formula based on the INDEX function:
=INDEX(myrange, x,y)
I would like to build a macro that locates the value found value by INDEX and moves the focus there, that is a macro changing the active cell to:
Range("myrange").Cells(x,y)
Doing the job without macros (slow but it works)
Apart from trivially moving the selection to myrange and manually counting x rows y and columns, one can:
Copy and paste the formula in another cell as follows:
=CELL("address", INDEX(myrange, x,y))
(that shows the address of the cell matched by INDEX).
Copy the result of the formula above.
Hit F5, Ctrl-V, Enter (paste the copied address in the GoTo dialog).
You are now located on the very cell found by the INDEX function.
Now the challenge is to automate these steps (or similar ones) with a macro.
Tentative macros (not working)
Tentative 1
WorksheetFunction.CELL("address", ActiveCell.Formula)
It doesn't work since CELL for some reason is not part of the members of WorksheetFunction.
Tentative 2
This method involves parsing the INDEX-formula.
Sub GoToIndex()
Dim form As String, rng As String, row As String, col As String
form = ActiveCell.Formula
form = Split(form, "(")(1)
rng = Split(form, ",")(0)
row = Split(form, ",")(1)
col = Split(Split(form, ",")(2), ")")(0)
Range(rng).Cells(row, CInt(col)).Select
End Sub
This method actually works, but only for a simple case, where the main INDEX-formula has no nested subformulas.
Note
Obviously in a real case myrange, x and ycan be both simple values, such as =INDEX(A1:D10, 1,1), or values returned from complex expressions. Typically x, y are the results of a MATCH function.
EDIT
It was discovered that some solutions do not work when myrange is located on a sheet different from that hosting =INDEX(myrange ...).
They are common practice in financial reporting, where some sheets have the main statements whose entries are recalled from others via an INDEX+MATCH formula.
Unfortunately it is just when the found value is located on a "far" report out of sight that you need more the jump-to-the-cell function.
The task could be done in one line much simpler than any other method:
Sub GoToIndex()
Application.Evaluate(ActiveCell.Formula).Select
End Sub
Application.Evaluate(ActiveCell.Formula) returns a range object from which the CELL function gets properties when called from sheets.
EDIT
For navigating from another sheet you should first activate the target sheet:
Option Explicit
Sub GoToIndex()
Dim r As Range
Set r = Application.Evaluate(ActiveCell.Formula)
r.Worksheet.Activate
r.Select
End Sub
Add error handling for a general case:
Option Explicit
Sub GoToIndex()
Dim r As Range
On Error Resume Next ' errors off
Set r = Application.Evaluate(ActiveCell.Formula) ' will work only if the result is a range
On Error GoTo 0 ' errors on
If Not (r Is Nothing) Then
r.Worksheet.Activate
r.Select
End If
End Sub
There are several approaches to select the cell that a formula refers to...
Assume the active cell contains: =INDEX(myrange,x,y).
From the Worksheet, you could try any of these:
Copy the formula from the formula bar and paste into the name box (to the left of the formula bar)
Define the formula as a name, say A. Then type A into the Goto box or (name box)
Insert hyperlink > Existing File or Web page > Address: #INDEX(myrange,x,y)
Adapt the formula to make it a hyperlink: =HYPERLINK("#INDEX(myrange,x,y)")
Or from the VBA editor, either of these should do the trick:
Application.Goto Activecell.FormulaR1C1
Range(Activecell.Formula).Select
Additional Note:
If the cell contains a formula that refers to relative references such as =INDEX(A:A,ROW(),1) the last of these would need some tweaking. (Also see: Excel Evaluate formula error). To allow for this you could try:
Range(Evaluate("cell(""address""," & Mid(ActiveCell.Formula, 2) & ")")).Select
This problem doesn't seem to occur with R1C1 references used in Application.Goto or:
ThisWorkbook.FollowHyperlink "#" & mid(ActiveCell.FormulaR1C1,2)
You could use the MATCH() worksheet function or the VBA FIND() method.
EDIT#1
As you correctly pointed out, INDEX will return a value that may appear many times within the range, but INDEX will always return a value from some fixed spot, say
=INDEX(A1:K100,3,7)
will always give the value in cell G3 so the address is "builtin" to the formula
If, however, we have something like:
=INDEX(A1:K100,Z100,Z101)
Then we would require a macro to parse the formula and evaluate the arguments.
Both #lori_m and #V.B. gave brilliant solutions in their own way almost in parallel.
Very difficult for me to choose the closing answer, but V.B. even created Dropbox test file, so...
Here I just steal the best from parts from them.
'Move to cell found by Index()
Sub GoToIndex()
On Error GoTo ErrorHandler
Application.Goto ActiveCell.FormulaR1C1 ' will work only if the result is a range
Exit Sub
ErrorHandler:
MsgBox ("Active cell does not evaluate to a range")
End Sub
I associated this "jump" macro with CTRL-j and it works like a charm.
If you use balance sheet like worksheets (where INDEX-formulas, selecting entries from other sheets, are very common), I really suggest you to try it.