How do you get a UserForm to activate on double click of a cell? - vba

I haven't been able to get this to work for a long time now after trying a lot of different things I've found online.
I am trying to get a UserForm called "UserComment" to open and activate when I double click a cell (within the columns of B-K and and rows starting at 2 down to the last row of data) on a sheet called Open_Orders.
This is the code I have pasted into the Open_Orders sheet area (not a module).
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim Lastrow As Integer
Lastrow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
Set rng1 = Open_Orders.Cells(2, 2)
Set rng2 = Open_Orders.Cells(11 & Lastrow)
Set NewRng = .Range(rng1, rng2)
If Not Application.Intersect(Target, Range("NewRng")) Is Nothing Then
Cancel = True
UserComment.Show
End If
End Sub
When I click on a cell I get the error "Compile Error: Invalid of unqualified reference" and it highlights in blue the ".Range" section of where I set NewRng.
Any ideas of what I can do to fix this?
Also, this is a protected sheet with a password "maintenance"...not sure why there was no problem there.
EDIT: I have added the following code as the first line of code and I still get the same error:
Open_Orders.Protect Password:="maintenance", UserInterFaceOnly:=True
EDIT AGAIN: Gary's Student helped in the comments below by changing Range("NewRng") to to just NewRng....however it still doesn't work because it says its protected EVEN THOUGH I have that code above that should allow it to work.
***UPDATE***
For some reason, when I click the first row of data (row 2), it prompts for a password, and if I press cancel, then the correct userform pops up. No idea what is going on.

you need to qualify your .Range(rng1,rng2) with a sheet reference. If you want the range to be the currently active sheet, then remove the . (dot) in front of .Range(rng1,rng2).

I would agree with sous2817 above: it is going to be much easier to unprotect the entire sheet, and then re-protect it when the form unloads. Since you are displaying the form modally, the user cannot interact with the worksheet anyways, so that preserves enough "protection" -- when the form is modally displayed, the user can't edit it. So just unprotect the sheet, then show the form, then re-protect the sheet in the userform's Terminate event handler.
Here are a few additional observations:
This is a problem because you're doing string concatenation:
Set rng2 = Open_Orders.Cells(11 & Lastrow)
The result of 11 & LastRow is not going to be what you expect. If Lastrow is "33" for example, the result of this expression will be 1133 which is an invalid range assignment. There simply is no such thing as Range(11) or Range(1133), etc. (any numeric value like this will fail with the same error). Try this instead:
Set rng2 = Open_Orders.Cells(11, Lastrow)
Likewise, this is not a valid range:
Range("NewRng")
You have a variable called NewRng which is a range object. When you do "NewRng" you're passing a literal string. Unless you actually also have a Name defined on your worksheet like this (highly unlikely), this will always raise an error because it's a non-existent range. Try this instead:
If Not Application.Intersect(Target, NewRng) Is Nothing Then
I suggest you should read here about how to debug your code. Most of your problems are common mistakes that you will be able to resolve on your own.
http://www.cpearson.com/excel/DebuggingVBA.aspx

How about:
Set NewRng = Open_Oders.Range(rng1, rng2)

Related

Can't for the life of me figure out what is causing this mismatch error VBA Excel?

I have this code which is causing a mismatch error and I can't figure out why I had it working sort of before with the mismatch error and in an attempt to fix it I can't get it back to working. The values in the merge sheet are all numeric. Basically what I was trying to do was when a value is entered into a cell there would be a VLookup would be executed to input a value into the adjacent cell and once I get this working, more cells in the same row. If any of you are itching to fix something just let me know!
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim LooupValue As String
Dim sfx As Long
Set KeyCells = Columns(1)
LooupValue = ActiveCell.Value
sfx = Application.VLookup(LooupValue, Worksheets("Merge").Range("D:BD"), 2, False)
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
Range(Target.Address).Offset(0, 1).Value = sfx
End If
End Sub
Edit: Thanks to #Marcucciboy2, #MathieuGuindon, and #BigBen for their successful help, I have done some more research and posted what solved my issue down below.
Dim sfx As Long
sfx = Application.VLookup(LooupValue, Worksheets("Merge").Range("D:BD"), 2, False)
If the vlookup yields xlErrNA i.e. #N/A, then VBA can't coerce its result into a Long, and you get exactly that: a type mismatch error - because xlErrNA is an Error value, which can't be implicitly converted to a String, a Long, or anything. The only type that can contain this data, is a Variant.
Dim result As Variant
result = Application.VLookup(LooupValue, Worksheets("Merge").Range("D:BD"), 2, False)
Dim sfx As Long
If Not IsError(result) Then
sfx = CLng(result)
Else
'lookup yielded no match
End If
Also, it looks like this is off-by-one:
LooupValue = ActiveCell.Value
The ActiveCell likely isn't the same cell as Target, which is the cell that was modified. You probably need this instead:
LookupValue = Target.Value
I'd also recommend making unqualified Range (same with Rows, Colomns, Names, and Cells) calls explicitly qualified - because that exact same code behaves differently depending on where it's written in. By qualifying them with Me, you're making your code more explicit. Code that says what it does, and does what it says, is always better code.
Worksheets("Merge") is a smell: if the sheet exists in ThisWorkbook at compile-time, give it a code name (i.e. set its (Name) property) and use that identifier directly:
result = Application.VLookup(LooupValue, MergeSheet.Range("D:BD"), 2, False)
If the sheet only exists at run-time (e.g. it's in a workbook that was opened by the macro), then you should have a reference to that workbook near where you opened that file, e.g. Set book = Application.Workbooks.Open(path) - use that reference to qualify the Worksheets member call, instead of implicitly referring to ActiveWorkbook.
The lookup range D:DB is excessive when you're only looking for the value in column E. If that hard-coded 2 is there to stay, I'd make the lookup range be D:E.
I would also nest the value setting within the "If" that checks for intersection; otherwise, every time you change the worksheet it does an unnecessary vlookup in the background.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Application.Intersect(Target, Range("A:A")) Is Nothing Then
Target.Offset(0, 1).value = Application.VLookup(Target.value, Worksheets("Merge").Range("D:BD"), 2, False)
End If
End Sub
I figured out what the last problem was... The lookup column was, for some reason from the previous document editor, formatted as text. The reason why only some lookup values were working was because even after I formatted the entire column to numbers, the said values were reformatted as numbers only after I had edited the cell. I wasn't going to edit every single cell so I did some research and found out how to refresh an entire column's format.
I refreshed the column by doing the following:
Select the entire column
Format the column as necessary
Go to the Data ribbon and select 'Text-to-Columns'
Ensure Delimited is selected, click Next
Uncheck all the Delimiters, click Next, then Finish

Possible to Lock Range of cells based on another cell's value?

I am using a tracker for testing new changes, and when. If a new change is N/A, I don't want to delete it, I want to disable (and turn gray) all the cells that are available to select the date it was completed. But only in that row. I've tried using the following methods but haven't had luck with any:
Conditional Formatting on the sheet
VBA code that executes when a change is made
Data Validation (think this is possible but am unfamiliar with how this really works)
Here is the code I have:
Private Sub worksheet_change(ByVal Target As Range)
Dim keycells As Range
Set keycells = Range("G:G")
lastcol = CInt(Sheet1.Cells(1,Sheet1.Columns.Count).End(xlToLeft).Column)
If Not Application.Intersect(keycells, Range(Target.Address)) Is Nothing Then
r = Range(Target.Address).Row
MsgBox "There was a change"
If Range(Target.Address).Value = "N/A" Then
MsgBox "we got this far"
Range("H" & r & ":" & Cells(r, lastcol).Address).Locked = True
End If
End If
End Sub
Both message boxes show, but the cells don't lock. So I tried unlocking all cells, then protecting the sheet. Then I set something to "N/A" and get the error "Unable to set the Locked property of the Range Class".
Here is an idea of what my data looks like:
Thanks in advance!
Well, Community has been insisting (for days) that I look at this question, relentlessly pushing it to the top of my "relevant" queue, probably because it's tagged with my top 4 tags, and it doesn't technically have an Answer.
So, sorry Moacir, I'm swiping your "commented answer":
Leave it protected,
do Worksheet.Unprotect,
run your code (and Worksheet.Protect) after that.
More Info:
Microsoft : Worksheet.Protect Method (Excel)
Microsoft : Worksheet.Unprotect Method (Excel)
S.O. : How to protect cells in Excel but allow these to be modified by VBA script

I have failed endlessly trying to write the code in VBA to insert this formula into a cell

The formula:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9))," ")
An example of what I've tried in VBA:
Sub inputWorkdays()
Range("h9").Formula = "=IFERROR(IF(OR(E9=0,D9=0),0,NETWORKDAYS(D9,E9)),""Yes"")"
End Sub
I'm trying to add the formula from above into cell H9.
Select the cell with the formula and write the following:
Sub TestMe
debug.print Selection.Formula
debug.print Selection.FormulaR1C1
End sub
In your case it would give:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),"YES")
=IFERROR(IF(OR(R[-4]C[-1]=0,R[-5]C[-2]=0),0,NETWORKDAYS(R[-5]C[-2],R[-5]C[-1])),"YES")
Take the first one and use it like this:
Range("h9").Formula = "=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),""YES"")"
I gather from the comments that there is no error, just "nothing happens". I see nothing wrong with your code. Except...
Range("h9").Formula = "..."
When Range is unqualified like this, you implicitly refer to the ActiveSheet; if the active sheet isn't the sheet you're expecting to write to, then it's easy to conclude that "nothing happens" and that the code doesn't work.
If you have Rubberduck installed (full disclosure: I'm heavily involved with the development of this open-source VBE add-in), you will see that Range in this case is a member of Excel._Global, and an inspection result will tell you that you're implicitly referring to the ActiveSheet:
Range("H9").Formula = "..."
Implicit references to the active sheet make the code frail and harder to debug. Consider making these references explicit when they're intended, and prefer working off object references.
http://rubberduckvba.com/Inspections/Details/ImplicitActiveSheetReferenceInspection
To fix this, qualify the Range call with a Worksheet object - now the Range call is a member of the Excel.Worksheet class:
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("Sheet1")
sheet.Range("H9") = "..."
By qualifying Range calls with a worksheet object, you make sure that you're always writing to the worksheet you mean to write to - not the worksheet that happens to be the active one when the code runs.

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.

Referring to Dynamic Named Ranges in VBA

I'm having troubling referring to a Dynamic Name Range in VBA.
My ranges are defined as
=OFFSET(Sheet!$B$2,0,0,COUNTA(Sheet!$B:$B)-1,1)
My code should search one range for all entries in another range, the intention being that any missing entries will be added. So far I have
Sub UpdateSummary()
Dim Cell As Range
Dim rngF As Range
Set rngF = Nothing
' Step through each cell in data range
For Each Cell In Worksheets("Aspect").Range("A_Date")
' search Summary range for current cell value
Set rngF = Worksheets("Summary").Range("Sum_Date").Find(Cell.Value) // Does not work
If rngF Is Nothing Then
' Add date to Summary
End If
Set rngF = Nothing
Next Cell
End Sub
The For loop seems to work ok. However, using the .Find method is giving me an error message.
Application-defined or object-defined error
It does work if I replace the named range with a specific range ($B$2:$B$5000), so it seems to be down to how the named range is being passed.
Any ideas would be appreciated.
Thanks.
The error is almost definitely because Excel can't find a named range Sum_Date that refers to a range on a worksheet named Summary. The most common causes are
Sum_Date refers to a sheet other than Summary. Check the RefersTo property of Sum_Date and make sure nothing is misspelled.
There is not a named range Sum_Date, that is, it's misspelled in the VBA code. Check the spelling of the named range in the Name Manager.
There is an error in the RefersTo formula of Sum_Date. It sounds like you already verified that this isn't the case.
I've had the a similar if not the same problem & here's how I solved it:
I first realized that the method I used to create my named range, using the Name Manager, my named range had a scope of Workbook. This is important because, it doesn't belong to the worksheet, & therefore will not be found there.
So, Worksheets("Summary").Range("Sum_Date") would not work for me.
Since my range belonged to the workbook, the way I was able to find is to use ActiveWorkbook.Names("Sum_Date")
For me I used it to remove the formula from named range that I am using in many places. The huge advantage is that named range is updated only once instead of the formula being called for every cell location that ranged is called. Huge time delay difference!
Public last_Selection As String
Private Sub Worksheet_Change(ByVal Target As Range)
'excel data change detection
If Range(last_Selection).Column = 2 Then
'Disable events, so this only executes once
Application.EnableEvents = False
'This can be done with a complex formula in a cell,
'but this is easily understood
Range("B1").End(xlDown).Select
ActiveWorkbook.Names("last_Entry").Value = ActiveCell.Row
'Re-enable so this routine will execute on the next change
Application.EnableEvents = True
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'constantly store the last cell to know which one was previously edited
last_Selection = Target.Address
End Sub
I know this is a very old thread, but I had the same issue today and I was looking for solution for quite a long time. So maybe this will help someone.
The named "range" defined by the =OFFSET(...) formula is actually a named FORMULA, so in VBA you have to evaluate it first to get the range. E.g.:
Set rgNamedRange = Worksheets("Summary").Evaluate("Sum_Date")
Credits to a guy named "shg" from mrexcel.com, who got me on right track. :)
I have been experimenting with this for a few days and eventually I came up with the following. It may not be the most efficient but it did work for me!
The named range of "OhDear" was set up in the normal way
Dim vItem As Variant
Set vItem = Names("OhDear")
Debug.Print vItem.Name
Worth a try don't you think!
This does not work if instead of using a variant you use something like: Dim Nm as Name: Set Nm = Names("OhDear"). Any variations using 'Nm' failed!!!