I am new at vba and I have a rather simple issue. I want to obtain the address of the last active cell there was. For example, if I was at A5 and moved to B6, is there a command to obtain the address A5?
Any type of tip or suggestion is highly appreciated!
I've tried ActiveCell.Previous but that provides the address of the cell on the left of the active cell. Offsets are no use for me since the address A5 is unknown until the user changes something inside a grid of cells.
The most obvious way would be to use Excel's events. You could have a look at the SelectionChange event, which would enable you to store the previous selection at module-level and then retrieve that value on subsequent firings of the event.
In the example below I've used the code-behind of the Workbook object, as it enables you to register selections on any sheet, but you could do the same on just one Worksheet.
If you're only interested in certain cells, then look at the Intersect function to refine the routine.
Option Explicit
Private pPreviousWorksheet As Worksheet
Private pPreviousSelection As Range
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
'Check SH object is a worksheet.
If TypeOf Sh Is Worksheet Then
'Check we have instances of previous objects.
If Not pPreviousWorksheet Is Nothing And Not pPreviousSelection Is Nothing Then
'Process code here...
MsgBox "Previous was " & _
pPreviousWorksheet.Name & "!" & _
pPreviousSelection.Address(False, False)
End If
'Re-set the previous objects.
Set pPreviousWorksheet = Sh
Set pPreviousSelection = Target
End If
End Sub
Related
I am building an Excel 2016 Userform using VBA and need to collect the row and column of the cell from which the form is opened. I open the form on a cell double click with Worksheet_BeforeDoubleClick and then initialize the Userform with UserForm_Initialize(). I would like to pass the Target of the double click event to UserForm_Initialize() but am not sure how to. This forum thread addresses this issue, but the provided solutions did not work for me.
Here is my Worksheet_BeforeDoubleClick:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Column = Target.Column
Row = Target.Row
'Find the last non-blank cell in column B(2)
lRow = Cells(Rows.Count, 2).End(xlDown).Row
'Find the last non-blank cell in row 2
lCol = Cells(2, Columns.Count).End(xlToRight).Column
If Not Intersect(Target, Range(Cells(3, 3), Cells(lRow, lCol))) Is Nothing Then
Cancel = True
EdgeEntryForm.Show
End If
End Sub
And my UserForm_Initialize():
Private Sub UserForm_Initialize()
Dim Column As Long, Row As Long 'I would like to fill these with the Target values
MsgBox ("Row is " & Row & " Column is " & Column)
'Description.Caption = "Fill out this form to define a network edge from " & Cells(2, Row).Value & " to " & Cells(Column, 2).Value
End Sub
As suggested in my comments, one way would be to just use the ActiveCell and assign that to a variable.
Alternatively, if you do want to pass it as a variable, you can do it with a bit of a workaround, by having a global variable to temporarly hold that information:
In your worksheet code:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'.....
With UserForm1
Set .rngTarget = Target
.Show
End With
'.....
End Sub
In your userform:
Public rngTarget As Range
Private Sub UserForm_Activate()
'....
If Not rngTarget Is Nothing Then
MsgBox ("Row is " & rngTarget.Row & " Column is " & rngTarget.Column)
Else
MsgBox "something went wrong with assigning rngTarget variable"
End If
'....
End Sub
EDIT: I was trying initially to propose something similar to #MathieuGuindon's answer, but was failing due to my limited knowledge on the difference between initialise and activate (thanks Mathieu).
I've updated the answer to make use of the global variable at userform level, rather than use one from a module.
The form is shown modally, so ActiveCell isn't going to change on you, and should be safe to use in the form's code-behind.
The problem with that, is that you've now tied the form to ActiveSheet/ActiveCell, and now in order to test anything you need to Select or Activate a cell.
If the form code only needs to know about the cell's Address, then it shouldn't be given a Range (give it a Range and it can access any cell in any sheet in any workbook in the Application instance) - that's the principle of least knowledge at play. But this is obviously example code, so let's go with a Range:
Option Explicit
Private internalWorkingCell As Range
Public Property Get WorkingCell() As Range
Set WorkingCell = internalWorkingCell
End Property
Public Property Set WorkingCell(ByVal value As Range)
Set internalWorkingCell = value
End Property
Now your form code can use WorkingCell or internalWorkingCell to do its thing, and no global variable needs to float around;
With New UserForm1 ' Initialize handler runs here
Set .WorkingCell = Target
.Show ' Activate handler runs here
End With
The WorkingCell belongs to the form - it has no business being in global scope.
Careful with the Initialize handler in forms - especially when you use its default instance (i.e. when you don't New it up): you don't control when that handler runs, the VBA runtime does; UserForm_Initialize will run the first time the form instance is referenced (in your case, immediately before the .Show call), and then never again unless the instance is destroyed (clicking the red X button would do that).
A MsgBox call in the Initialize handler will run before the form is shown; you probably want to move that code to the Activate handler before it causes problems.
I have a series of 2 combo boxes. I have a macro called Generate which will change the options in the second combo box based on the number the first combo box returns. However this requires the user to press a button to execute this macro. I would like this macro to execute automatically when the number in the first combo box's linked cell changes.
This is the code I have previously tried, however the change in the link cell which is B2 doesn't seem to trigger the event.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("B2")) Is Nothing Then Generate
End Sub
As mentioned before, a Worksheet_Change event will only be triggered by physically changing a cell, and thus your linked cell won't have any effect.
If it's a cell in another wb that needs to trigger your Generate Sub, then I'd advise using a Worksheet_Change event for said wb.
In my own project, I have a sub in a regular module:
Dim AddNew As Workbook
Set AddNew = Workbooks("") 'change this
Set oWb.Workbook = AddNew
Then, in a class module:
Public WithEvents m_wb As Workbook
Public Property Set Workbook(wb As Workbook)
Set m_wb = wb
End Property
Public Property Get Workbook() As Workbook
Set Workbook = m_wb
End Property
Public Sub m_wb_SheetChange(ByVal Sh As Object, ByVal Target As Range)
'In here, you could trigger the Generate Sub if a specific cell changes
End Sub
The simplest solution was to give the first combo box an input range and also assign it the Generate macro
I created a worksheet_calculate event macro to return a message box (CHANGE DETECTED!) whenever the value in the cells W4656:W4657 change. These values are referenced from another sheet in the same workbook.
My problem is the worksheet_calculate event is fired whenever data is entered anywhere in the workbook.
Could this be modified such that the worksheet_calculate event is fired only when data in a specific cell (a cell in a different sheet) is changed.
Private Sub Worksheet_Calculate()
Dim Xrg As Range
Set Xrg = Range("W4656:W4657")
If Not Intersect(Xrg, Range("W4656:W4657")) Is Nothing Then
MsgBox ("CHANGE DETECTED!!")
ActiveWorkbook.Save
End If
End Sub
Well, if we examine these lines of your code
Dim Xrg As Range
Set Xrg = Range("W4656:W4657")
If Not Intersect(Xrg, Range("W4656:W4657")) Is Nothing Then
Since we set Xrg, then immediately use it, we can rewrite that as
If Not Intersect(Range("W4656:W4657"), Range("W4656:W4657")) Is Nothing Then
which will always be true. So, every time the worksheet Calculates, it will say "CHANGE DETECTED!"
Ideally, you want to store the values in those Cells somewhere, and then just run a comparison between the cells and the stored values. Using Worksheet Variables, you could get the following: (You could also store the values in hidden worksheet as an alternative)
Option Explicit 'This line should almost ALWAYS be at the start of your code modules
Private StoredW4656 As Variant 'Worksheet Variable 1
Private StoredW4657 As Variant 'Worksheet Variable 2
Private Sub Worksheet_Calculate()
On Error GoTo SaveVars 'In case the Variables are "dropped"
'If the values haven't changed, do nothing
If (Me.Range("W4656").Value = StoredW4656) And _
(Me.Range("W4657").Value = StoredW4657) Then Exit Sub
MsgBox "CHANGE DETECTED!", vbInformation
SaveVars:
StoredW4656 = Me.Range("W4656").Value
StoredW4657 = Me.Range("W4657").Value
End Sub
So I've managed to find a solution (work around?) to my problem.
I ended up using a macro to check if the the number in Sheet 38, Cell W4656 which was referenced from Sheet 5, Cell J2, has changed. If yes, fire a macro. If not, do nothing.
I've realized that with the code below, worksheet_calculate event is fired only when there is change in Sheet 5, Cell J2 or Sheet 38, Cell W4656 which is what I want.
Private Sub Worksheet_Calculate()
Static OldVal As Variant
If Range("w6").Value <> 24 Then
MsgBox ("XX")
'Call Macro
End If
End Sub
I've updated my code and made it cleaner, and shamelessly stole some of
Chronocidal's approach (my original code required the workbook to be closed and opened to work). So here is what Sheet5 looks like in my example:
And here is Sheet38. In my example I simply setup formulas in Sheet38!W4656:W4657 to equal Sheet5!$J$2 ... so when Sheet5!$J$2 changes so does Sheet38!W4656:W4657 which will trigger the code.
And copy this code into ThisWorkbook ...
Option Explicit
Dim vCheck1 As Variant
Dim vCheck2 As Variant
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If vCheck1 <> Sheet38.Range("W4656") Or vCheck2 <> Sheet38.Range("W4657") Then
MsgBox ("CHANGE DETECTED!!")
Application.DisplayAlerts = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
vCheck1 = Sheet38.Range("W4656")
vCheck2 = Sheet38.Range("W4657")
End If
End Sub
Like this ...
I have a worksheet that has a bunch of dynamic hyperlinks that change based on a drop down menu. Only the cells with drop down menus are unlocked. I have "select locked cells" unchecked so that when I protect the sheet, users can only select the drop down menus. Unfortunately, when I do this, the hyperlinks are no longer usable.
Does anyone know how to work around this?
UPDATE*
as requested, the code for my dynamic hyperlink cells:
=IF(ISNA(MATCH(B4,'Data Sheet'!A2:A103,0)),"",HYPERLINK(VLOOKUP(B4,'Data Sheet'!A:S,7,FALSE),VLOOKUP(B4,'Data Sheet'!A:S,5,FALSE)&" - "&VLOOKUP(B4,'Data Sheet'!A:S,6,FALSE)))
1) Cell B4 is the drop down where the user selects a particular option. The hyperlinks change based on this selection.
2) 'data sheet' is a separate sheet that houses all of the reference data in an array.
this basically says: does the value in B4 match the first column in my data chart? if so, use a hyperlink formula using VLOOKUP to insert the corresponding URL into the formula.
This is my understanding of the settings and requirements:
Settings
There is a protected worksheet with a dropdown menu which updates other cells containing VLOOKUP\HYPERLINK formulas.
All cells in the worksheet, excluding the dropdown menus, are protected.
The value of the cells containing VLOOKUP\HYPERLINK formulas, could equal to a www address or blank depending on the value of the dropdown menu. As such, all hyperlinks point to web pages or are blank.
The worksheet EnableSelection is set to xlUnlockedCells which determines that once the worksheet is protected “Only unlocked cells can be selected.”
Requirements
- Need to maintain the worksheet protected to safeguard al the contents including the VLOOKUP\HYPERLINK formulas.
Need to allow users to select\activate only unprotected cells mostly for aesthetic reasons and to deliver a professional product.
This solution uses the following resources
The HYPERLINK function
An UDF (user defined function)
Two Public Variables and
The Worksheet_BeforeDoubleClick event
When an UDF is wrapped into a HYPERLINK function it causes that
every time the mouse hovers over the cell containing the combined
formula of HYPERLINK(UDF,[FriendlyName]) the UDF is triggered.
We’ll use a Public Variable to hold the LinkLocation, to be used later to follow the hyperlink upon users decision.
And a second Public Variable to set the time when the LinkLocation was last updated.
We’ll mimic the manner in which the hyperlink is “normally” activated:
by which an user selects a cell and clicks the hyperlink in the selected cell.
Instead the user hovers over the cell with the hyperlink (the UDF feeds the LinkLocation and the time into the public variables) and DoubleClicks the cell (triggering the worksheet event to follow the hyperlink, validating first the time when the LinkLocation was last updated to ensure it stills actual and clearing the LinkLocation variable).
First we need to ensure that the formulas used in the worksheet to generate the Dynamic Hyperlinks have the appropriated structure:
Assuming the current VLOOKUP\HYPERLINK formulas have the following structure:
(have to work based on assumptions as the actual formula was not provided)
=IFERROR( HYPERLINK( VLOOKUP( DropDownCell , Range , Column, False ), FriendlyName ), "" )
We need to change that formula to the following structure:
=IFERROR( HYPERLINK( UDF( VLOOKUP( DropDownCell , Range , Column, False ) ), FriendlyName ), "" )
The following procedures take care of modifying the formulas structure to make them suitable for the solution proposed. Suggest to copy both in a separated module named “Maintenance”.
Option Explicit
Private Sub Wsh_FmlHyperlinks_Reset()
Const kWshPss As String = "WshPssWrd"
Const kHypLnk As String = "HYPERLINK("
Dim WshTrg As Worksheet, rHyplnk As Range
Dim rCll As Range, sHypLnkFml As String
Dim sOld As String, sNew As String
Rem Application Settings
Application.EnableEvents = False
Application.ScreenUpdating = False
Rem Set & Unprotect Worksheet
Set WshTrg = ActiveSheet
WshTrg.Unprotect kWshPss
Rem Find Hyperlink Formulas
If Not (Rng_Find_Set(WshTrg.UsedRange, _
rHyplnk, kHypLnk, xlFormulas, xlPart)) Then Exit Sub
If rHyplnk Is Nothing Then Exit Sub
Rem Add Hyperlinks Names
For Each rCll In rHyplnk.Cells
With rCll
sHypLnkFml = .Formula
sOld = "HYPERLINK( VLOOKUP("
sNew = "HYPERLINK( Udf_HypLnkLct_Set( VLOOKUP("
sHypLnkFml = Replace(sHypLnkFml, sOld, sNew)
sOld = ", FALSE ),"
sNew = ", FALSE ) ),"
sHypLnkFml = Replace(sHypLnkFml, sOld, sNew)
.Formula = sHypLnkFml
End With: Next
Rem Protect Worksheet
WshTrg.EnableSelection = xlUnlockedCells
WshTrg.Protect Password:=kWshPss
Rem Application Settings
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Function Rng_Find_Set(rInp As Range, rOut As Range, _
vWhat As Variant, eLookIn As XlFindLookIn, eLookAt As XlLookAt) As Boolean
Dim rFound As Range, sFound1st As String
With rInp
Set rFound = .Find( _
What:=vWhat, After:=.Cells(1), _
LookIn:=eLookIn, LookAt:=eLookAt, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not (rFound Is Nothing) Then
sFound1st = rFound.Address
Do
If rOut Is Nothing Then
Set rOut = rFound
Else
Set rOut = Union(rOut, rFound)
End If
Set rFound = .FindNext(rFound)
Loop While rFound.Address <> sFound1st
End If: End With
Rem Set Results
If Not (rOut Is Nothing) Then Rng_Find_Set = True
End Function
These are the Public Variables and the UDF. Suggest to copy them in a separated Module.
Option Explicit
Public psHypLnkLoct As String, pdTmeNow As Date
Public Function Udf_HypLnkLct_Set(sHypLnkFml As String) As String
psHypLnkLoct = sHypLnkFml
pdTmeNow = Now
End Function
And copy this procedure in the Module of the protected worksheet with the dynamically generated hyperlinks.
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Now = pdTmeNow And psHypLnkLoct <> Empty Then
ThisWorkbook.FollowHyperlink Address:=psHypLnkLoct, NewWindow:=True
End If
End Sub
If you are happy to use VBA, you could use the following code for the Sheet(s) in question, this will replicate the click event for the hyperlink and try and open in the target's native format
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If InStr(1, Target.Formula, "HYPERLINK", vbTextCompare) > 0 Then
On Error Resume Next
Target.Hyperlinks(1).Follow (True)
On Error GoTo 0
End If
End Sub
Update
I think I have a bit of a work around. I've pinched some code from here which allows a roll over action to trigger some vba. So, let's say you have your link in cell A1. Change your link to the following:
=IFERROR(HYPERLINK(MyMouseOverEvent("http://www.google.com"),"Hover"),"Hover")
You can change your link dynamically providing it returns a string. Now create a new module and paste in the following:
Public Function MyMouseOverEvent(varLink As String)
varResponse = MsgBox("Would you like to open link to: '" & varLink & "'?", vbYesNo, "Confirm")
If varResponse = vbYes Then
ActiveWorkbook.FollowHyperlink Address:=varLink, NewWindow:=True
End If
End Function
The only drawback is that it fires the code on hover instead of on click, however the pop up box will allow the user to decide if they want to follow said link. I'll keep looking at it and see if I can find a work aorund for the click, but I think it's progressing as it will fire even when fully protected. I am using Excel 2010 if that helps.
My purpose is to run a macro automatically on some 20 cells across my active worksheet whenever these are edited. Instead of having the same macro in place for every cell individually (makes the code very long and clumsy), I want to create a for loop which goes something like this:
for i="A10","A21","C3" ... etc
if target.address = "i" then
'execute macro
end if
I'm not quite sure how to do this... maybe another way would be a better option?
I'd really appreciate your help in the matter - thank you very much indeed.
You can use the Worksheet_Change event. Below is sample code. You need to put the code on the sheet code section
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim rng As Range
Set rng = Range("A1:B5")
' If there is change in this range
If Not Intersect(rng, Target) Is Nothing Then
MsgBox Target.Address & " range is edited"
' you can do manipulation here
End If
Application.EnableEvents = True
End Sub
You can use the Worksheet_Change event to capture the edits. See http://msdn.microsoft.com/en-us/library/office/ff839775.aspx.
The event body receives a Range object that represents the modified cells. You can then use Application.Intersect to determine if one of your target cells is in the modified range.