Lock/Unlock Cell References Based on Conditional Logic - excel

I am creating a report request form in Excel that essentially forces the requester to go step by step within the document to ensure we have everything filled out. As a control, I'm looking to lock the inputs in the further steps until the current step has been completed (i.e. simply putting text in a cell). I've put names in the Name Manager in Excel for each of the input steps.
For the life of me I can't get my code to work. I've even just tried to implement a MsgBox to make sure the IF is working correctly, but nothing appears.
Name Manager References:
Cell Reference IsRequestDetailsFilled = Cell O2. This cell has formula where it's logic will simply state "Yes" or "No".
Cell Reference BusinessNeed = Merged Cells B13:F16
The objective is for the macro to refer to IsRequestDetailsFilled. If it is "No", then lock BusinessNeed. If it is "Yes", then unlock BusinessNeed.
My code:
Private Sub Is_RequiredRequesterDetails_Filled()
'Dim CurrentWorksheet As Worksheet
Dim IsRequestDetailsFilled As Range
Dim BusinessNeed As Range
'Set CurrentWorksheet = Worksheets("New Report Request")
Set IsRequestDetailsFilled = Range("IsRequestDetailsFilled").Value
Set BusinessNeed = Range("BusinessNeed").Value
If IsRequestDetailsFilled = "No" Then
MsgBox Prompt:="Locked"
'CurrentWorksheet.BusinessNeed.Locked = True
Else
MsgBox Prompt:="Unlocked"
'CurrentWorksheet.BusinessNeed.Locked = False
End If
End Sub
Maybe I'm going insane because I haven't stopped working since 6 AM and it's now 10:30 PM... but I can't seem to find this answer from Googling it. I've tried several alternatives. I want the code to be readable, so ideally I would like to use the Reference Names I've designated.
If there's a better way to achieve what I am saying, please feel free to suggest that too.
Thanks!

Consider the logic of this excerpt from your code.
Dim IsRequestDetailsFilled As Range
Set IsRequestDetailsFilled = Range("IsRequestDetailsFilled").Value
The value property of Range("IsRequestDetailsFilled") is a variant - maybe a number or a string - but IsRequestDetailsFilled is a range. The appearance is that you are conflating the range itself with its value. The value property is just one of the many properties every range has, like Row, Column, Font, Fill etc. It so happens that the Value property is the default and therefore can be omitted which makes it the source of countless miscomprehensions.
Below is the correct syntax for assigning a range object to a variable declared as range.
Dim IsRequestDetailsFilled As Range
Set IsRequestDetailsFilled = Range("IsRequestDetailsFilled")
In practice you may not need that. The following code will work just as well and is perhaps easier to read.
Private Sub Is_RequiredRequesterDetails_Filled()
If Range("IsRequestDetailsFilled").Value = "No" Then
MsgBox Prompt:="Locked"
'CurrentWorksheet.BusinessNeed.Locked = True
Else
MsgBox Prompt:="Unlocked"
'CurrentWorksheet.BusinessNeed.Locked = False
End If
End Sub
If Range("IsRequestDetailsFilled") is declared with a scope for the worksheet only the worksheet must be specified. By default, Excel will create named ranges with a workbook-wide scope. You can check and set the scope in the Name Manager. If the name was created with a workbook-wide scope VBA will find it anywhere in the workbook and you don't need to mention the tab in the code.

Related

Evaluating use of INDIRECT function taken from Cell Validation

I have a number of cells which have validation lists that are based on the use of the INDIRECT function. In this case there are two variables - one is the currently selected State (as reference through the named range dtState) and another based on a separate selection already made by the user in a different cell. The function in the Validation List for the cell is:
=INDIRECT("dtHdrPlates"&dtSize&dtState)
dtsize : could equal "70" or "90"
dtState : couldequal "VIC" or "QLD"
Naturally there is a named range for all combination of "dtHdrPlates" and the possible options of dtSize and dtState. The INDIRECT function in the Cell Validation list gives me a list that changes based on the selections in other cells.
dtHdrPlates70VIC
dtHdrPlates90VIC
dtHrdPlates70QLD
dthrdPlates90QLD
This works well thanks to advice previously obtained in this forum - thanks!
I am now trying to implement the use of a Combo Box over top of this cell, positioned, populated, made visible, activated and if relevant, dropped down by VBA Code; and removed once the use leaves the control.
Where I'm currently stuck is populating the ListFillRange of the ComboBox in the sheet - based on the INDIRECT function in the Validation.Formula1 of the cell the combo box is intended to supplant.
Sub BuildComboBox(rngCell as Range)
' This procedure checks to see if the Cell concerned is a list, and if so creates a combobox over top of the cell with the appropriate contents.
Dim rngCell as Range ' the cell concerned
Dim ws as Worksheet
Dim Dim cbo As OLEObject
If rngCell.Validation.Type = xlValidateList Then
Set ws = Excel.ActiveSheet
Set cbo = ActiveSheet.OLEObjects("ComboBox")
cbo.Left = rngCell.Left
cbo.Top = rngCell.Top
cbo.Width = rngCell.Width
cbo.Height = rngCell.Height
CBO.LISTFILLRANGE = RNGCELL.VALIDATION.FORMULA1
cbo.LinkedCell = rngCell.Address
If rngCell.Validation.ShowError = True Then
CBO.MATCHREQUIRED = TRUE
Else
CBO.MATCHREQUIRED = FALSE
End If
cbo.Visible = True
cbo.Activate
If rngCell.Value2 = "" Then
CBO.DROPDOWN
End If
End If
End Sub
I am stuck in three places in the above code (where the code is in CAPITALS).
What I would like to solve at the moment is how to get the indirect function taken from the cell validation list (rngCell.Validation.Formula1) and have it converted to the named range acceptable to cbo.ListFillRange - at the moment it comes across as =INDIRECT("dtHdrPlates"&sglLBWWallThickness&dtState) which does not work.
I've been playing with the EVALUATE function but just can't get it to parse the text coming from the Validation.formula1
If while you're looking at this you could also steer me towards how to change the status of an ActiveX ComboBox's MatchRequire property - and how to force it to DropDown, that would be lovely too.
Thanks, Regards, Ken

Excel VBA UDF Returns #VALUE Error When Adding Or Deleting Unrelated Sheet

First time posting, apologies if I make any mistakes!
So, I'm having a pretty strange problem with my UDF. In my workbook, I have an invisible 'template' sheet named "Standard Phase Sheet", and a subroutine that a user can activate which copies that template sheet into a new, visible sheet that the user can then work with. There will be many copies of that template sheet throughout the workbook, but they will all have unique names.
My UDF is on that template sheet in several spots, and thus on every copy of the template sheet that a user makes. When working within one of these sheets, the UDF works just fine, and returns the values I'd expect.
However, when a user ADDS a new copy of the template sheet, SOMETIMES the UDF goes haywire and returns #VALUE errors in every place the UDF is being used.
Also, when a user DELETES one of the copies of the template sheet, the UDF ALWAYS goes haywire and returns #VALUE errors in every place the UDF is being used.
I'm not using ActiveSheet or anything like that, and I believe I'm correctly giving full references to the ranges I'm working with within the UDF. Any help will be appreciated, I'm in a bind here! Code for the UDF is below.
Also, because I'm sure I'll be asked the question, the neColumn variable within my code is a public variable that I use in several subroutines and UDFs. It is defined at the beginning of my module. Also, I am using Option Explicit at the beginning of my module as well.
Thank you!
Public Function fSum(ByVal Target As Range, bExtended As Boolean) As Single
'This function returns a sum, based on a range provided in the cell that holds the function.
'It checks to see if that line item has been marked as Non-Extended, based on the NE column
'that can be check marked. If that line item is marked NE, then only the NE sum columns can
'use that line item as part of their sum, and those values are removed from the E columns.
Dim sSum As Single
Dim i As Integer
Dim n As Integer
'This small section is used to determine complete references to the cell calling the function.
Dim sheetName As String
sheetName = Application.Caller.Parent.Name
'Loop through provided range, and sum up the contents based on whether they have been marked NE or not.
i = 1
n = Target.row
sSum = 0
If Sheets(sheetName).Visible = True Then
While i < Target.Rows.Count
If (bExtended = True) Then
If Sheets(sheetName).Range(neColumn.Address).Cells(n, 1) = vbNullString Then
sSum = sSum + Sheets(sheetName).Range(Target.Address).Cells(i, 1).Value
End If
Else
If Sheets(sheetName).Range(neColumn.Address).Cells(n, 1) <> vbNullString Then
sSum = sSum + Sheets(sheetName).Range(Target.Address).Cells(i, 1).Value
End If
End If
i = i + 1
n = n + 1
Wend
End If
fSum = sSum
End Function
Summarizing the comment thread in an answer for posterity:
I'm not sure why exactly you see this behavior.
There would be ways to better this UDF (including using Long instead of Integer, preferring a Do While...Loop to While...Wend, removing the .Visible check...
But in any case, it does feel like this is just replicating the functionality of SUMIFS so you might just consider going that route.
The reason is that your neColumn variable has become Nothing, because Excel is Volatile.
I assume that the start of your module looks something like this:
Option Explicit
Public neColumn As Range
Sub Auto_Open()
Set neColumn = Sheet1.Range("A1:B2")
End Sub
When you open the Workbook, you call the Auto_Open Sub to Set the neColumn variable. However - when certain actions occur, Excel rebuilds the VBA, which resets the Public Variables (such as neColumn) to their defaults (which, for an Object such as a Range, is Nothing). An easy way to trigger this is by deliberately throwing an error, such as attempting to run this:
Sub ThrowErr()
NotDefined = 1
End Sub
You can make it more visible to you by adding the following line to your fSum code:
If neColumn Is Nothing Then Stop
You either need a way to restore neColumn when it has been reset to Nothing, OR find a non-volatile way to store it.
I am assuming that this is not suitable to become a Const, because otherwise it already would be but you could turn it into a Named Range, or store the Address in a hidden worksheet / CustomDocumentProperty. These options would also allow you to store neColumn when the Workbook is saved for when you reopen it

Excel VBA Evaluate Function Wrong When Reference is Not Active Sheet for Named Range

EDITED WITH BETTER EXAMPLE
I'm trying to use the Evaluate function to evaluate a formula reference for a named range. However, when using the Evaluate function, if you do not explicitly state the sheet reference along with the cell reference, it will assume the active sheet as the cell reference. This causes the wrong result
In my real project I'm trying to only evaluate a part of the named range's formula, so it makes it even trickier.
Using a basic example of what I'm trying to do, let's say you have the following formula in Sheet 1 cell A1 whose name is MyCell:
="Don't evaluate this part"&"My Result Is " & A2
If the Active Sheet is Sheet 2 and you run the following code it will give you the wrong results (this is a quick and dirty example to illustrate the problem)
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Debug.Print Evaluate(s)
Instead of giving me the value that is in cell A2 of Sheet 1, it gives me the value that is in cell A2 of Sheet2.
Any ideas around this?
This is closest I found, but it is not my exact problem (despite similar titles) and it doesn't provide a solution:
Excel VBA evaluate formula from another sheet
The problem you are having is that by design Excel will assume all unspecific cell references are referring to the existing worksheet. This is why whenever possible it is recommended to explicitly state the worksheet in all code.
The cleanest way (verified with some MSDN definintion hunting) is to just explicitly state the worksheet without activating it:
Sub test2()
Debug.Print Range("MyCell").Worksheet.Evaluate(Range("MyCell").Formula)
End Sub
Alternatively this code will change the active worksheet to the correct one and then change it back after evaluation. Not recommended to perform sheet activations like the code below without extenuating circumstances. Not even here.
Sub test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Range("MyCell").Worksheet.Activate ' Don't remember if .Worksheet or .Parent ??
Debug.Print Evaluate(s)
ws.Activate
End Sub
As pointed out in the comments by ThunderFrame, it is important to remember that this code assumes MyCell is a simple cell reference as stated in the question. Otherwise you will need to use other methods to determine the target worksheet name (or hardcode it).
Its nearly always better to use Worksheet.Evaluate rather than the default Application.Evaluate: as Mark Balhoff points out that allows you to control unqualified references.
But Worksheet.Evaluate is also usually twice as fast as Application.Evaluate.
See my blog post here for details
https://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast/
Your line:
Debug.Print Evaluate(Range("MyCell").Formula)
is equivalent to:
Debug.Print Evaluate("=""My Result Is "" & A2")
which is why you get results according to the value of A2 in the ActiveSheet.
If you want to inspect the contents of the formula, you can use this line:
Debug.Print [MyCell].Formula
If you want the value of MyCell with respect to Sheet1, then you have 2 options:
1 - Use Debug.Print Range("Sheet1!MyCell").Value
2 - Use Debug.Print Sheet1.Range("MyCell").Value

Use Named Range in UDF

I'm trying to create a UDF that will use a named range. My named range ("best_Grade") is a single cell, with a value. (The named range is scoped to the Workbook).
In a workbook module, when I try to create the variable using a named range, I get
Run-time error '1004': Method of 'Range' of object '_Global' failed
Neither line works:
Dim namedRng As Range
Dim locDataWS As Worksheet
Set locDataWS = Sheets("Approval matrix 16")
Set namedRng = Range("best_Grade") ' errors
Set namedRng = locDataWS.Range("best_Grade") ' When I take above line out, this line errors too
and I've tried:
Dim wb As Workbook
Set wb = ActiveWorkbook
Set namedRng = wb.Names("best_Grade").RefersToRange
How come it's erroring out? This page says it should be working, no? Do I have to put this UDF on the actual sheet object, and not in a workbook module?
Edit: Note: the named range is not set to a cell, but a SumIf formula (best_Grade = SumIf(A2:A10,"x",...)` which may be causing the error?
Edit2: Yeah, that's why I think. I created a named range for a random cell, and was able to use Range("a_grade").Value and it returned the expected value. Since my best_Grade range is a formula, I think that's why it's erroring out. However, I don't know why, as I'd think a named range is a named range, no matter what it's made up of...
Edit n+1: Note there are two "answers" to this. If I wanted to continue using a named range as a Range variable, see my answer below. However, what I really wanted to do was what #MacroMarc posted, so I chose that as the "Answer".
You need to use the Names collection instead:
Sub t5()
' named range "Test" is `=Sum($A$1:$A$4)`
Dim rng As Double
rng = Evaluate(Names("Test").Value)
Debug.Print rng
End Sub
There are various properties that Name objects have to return string representations.
Sub t5()
' named range "Test" is `=Sum($A$1:$A$4)`
Set wb = ActiveWorkbook
Dim rng As String
rng = wb.Names("Test").RefersTo
Debug.Print rng
rng = Application.Evaluate(wb.Names("Test").RefersTo)
Debug.Print rng
End Sub
After some testing I found the above worked...and kind of interesting. I got the lead from Macro Man's comment as well at Chip Pearson post.
The key is the defined names returns a string ="your result" so you can either evaluate it to get the answer, or you can do some string manipulation to pull off the quotes and equal sign. You really were close with your RefersToRange choice.
See Vegard's comment under your own posted answer.
The reason it wasn't working is because my named range best_Grades was not a cell reference, but a formula instead. Thus, when using Range("best_Grades").Value, it was erroring out. (best_Grades = SumIf(A2:A10,"x", B2:B10, ...)
Not sure why, since I'd think a named range is a named range, regardless of what makes that up...but I suppose not.
For now, my solution is just to create another named range, based on an actual cell value, and then use that. (theBest_Grades = A2). Then, I can call simply Range("theBest_Grades").Value without any issues.
I'll leave this open for a few days, in case someone has an idea of how I can keep my formula named range, and use that in VBA.
Edit: This was basically how I originally had the worksheet/named range:
with the named range being given as:
But, as I said, you can't use that type of named range in VBA (at least not that I have found).
So, to solve it, I just used that SumIf in the cell, and gave that cell the named range:
And now I can use Range("findWindow_Example").Value without issue.
Edit n+1:
I tried doing a simple test with a Double, same thing though, it errors out:
Sub t5()
' named range "Test" is `=Sum($A$1:$A$4)`
Dim rng As Double
rng = Range("Test") 'Run time error 1004
Debug.Print rng
End Sub
One thing I haven't seen mentioned here is that a ground rule for UDF's is broken here IMHO:
ALL information a UDF needs should be passed to the UDF through its arguments.
If you adhere to that rule, using any range name becomes simple, since the value of the range name will automagically be transferred to the argument.
The correct way to solve the problem has been found by more than one answerer, but as I said in the comments (and subsequently, thought might be of interest to others), the reason for the error wasn't specified yet.
The named range you defined does not return a range object. This means that this code:
Dim namedRng As Range
Set namedRng = Range("best_Grade")
couldn't possibly work (primarily because the named range returns a numerical value. If it returned a string address representation, it might have worked with some syntax improvements).
To illustrate this from the compiler's point of view, look at the print-outs in the immediate window here (the first line in particular):
If we assume the initial code to be pseudo-code, what was being asked of the compiler was to construct a range out of the formula (not its' result either!).
So if we swap Set namedRng = Range("best_Grade") for Set namedRng = Range(Names("namedRange")) the result might presumably (but not necessarily -- see end of post!) look like:
Set rng = Range("=SUMIF('Ark1'!$B$1:$B$5, "x", 'Ark1'!$A$1:$A$5)")
And of course, this would not work. But throwing namedRange into an Evaluate would, as the other answers demonstrate, be perfectly legal!
Interestingly, if we do ? Evaluate(Names("namedRange")) (omitting the .Value), we get an Error 2015, despite being able to ask the compiler ? Names("namedRange") and get a string in return!

Dynamically set ListFillRange in Excel ComboBox

I tried doing something like:
cmbMyBox.ListFillRange = "E2"
But the combobox does not seem to populate.
First of all, for trying to set the listfillrange from VBA, you need to include the '=' sign, like this:
combobox.ListFillRange = "=E3:E13"
and not combobox.ListFillRange = "E3:E13", this won't work.
You could have a dynamic named range, for example:
listItems: "=Sheet1!$A$1:INDEX(Sheet1!$A:$A;COUNTA(Sheet1!$A:$A))"
Use VBA to set the ListFillRange like this: combobox.ListFillRange = "=listItems"
Again, use the '=' sign!!
This dynamic range listItems grows/shrinks dynamically, depending on what values you have in column A.
I know I'm answering really late, but I noticed a lot of people thinking named ranges always have to be fixed, while they can be dynamic as well...
How do you do this?
In Excel 2007 and higher, you go to the ribbon-tab "Formulas" and click on the button "Name Manager"
Instead of selecting cells and giving them a name in that upper-left box, you can manage all your defined named ranges here.
Create a new one, and give it the value (without quotes):
"=Sheet1!$A$1:INDEX(Sheet1!$A:$A;COUNTA(Sheet1!$A:$A))".
There you go...
P.S. When you let the dynamic named range change, you should use VBA to re-set the .ListFillRange such that the combobox will refresh its list-items.
Ok, don't mean to answer my own question again but this ListFillRange property on combobox for Excel was absolutely maddening. Here's the final code that I implemented.
Sheet1.Range("E3").CurrentRegion.Select
Dim example as Range
Set example = Selection
With cmbMyBox
.ListFillRange = example.Address(0, 0, x1A1, True)
End With
The trouble here was that I was trying to dynamically set the combobox using a dynamic range which changes depending on what user inputted values were given. As far as I understand I couldn't use a named range because named ranges are FIXED, e.g (A3:Z20) and cannot be adjusted.
I was facing similar issue, not being able to populate the ActiveX ComboBox with list reference peeked from a cell's validation rule.
Similarly to Firedrawndagger's own solution I went for manually translating the validation rule to a format that is understood by the .ListFillRange.
I realised also, that it needs to be in a Workbook-scope format, otherwise the call will not work from other sheets.
This works with All validation source formats, including: $A$1 / =NamedRange / =INDIRECT("Table1[Col2]")
The translation was:
Dim xStr As String
xStr = Target.Validation.Formula1
xStr = Right(xStr, Len(xStr) - 1)
xStr = Split(Range(xStr).Address(, , , True), "]")(1)
'...other irrelevant code
.ListFillRange = xStr
Private Sub ComboBox1_Change()
Me.ComboBox2.ListFillRange = "=" & ComboBox1.Value
End Sub
This also works. But you have to do either the defined names with the index and counta as already suggested, or you can refer to them twice while in a table. What I mean by that, is make your data a table. Refer to the column you want as the title of your option from Combobox1 and add a one to the name, For example Fruits1, then define a secondary named range that refers to Fruits1 and is called Fruits.
Fruits
Fruits1
Alternatively, this is how I do it:
Define a range name that includes header and trailer rows plus one data row to start, say "DataList"
Then define the following data range name sans the header and trailer records using the offset function.
Say "DataRange" = Offset(DataList,1,0,Rows(DataList)-2)
This is working fine on Excel 2010:
I have a list of items in column "AN" that changes (get bigger/shorter) every week. I have created a variable called "c" which contains the number of items in the list. By using this variable, the ComboBox (positioned on cells S7:U7) will now dinamically show the items actually contained in the list (no empty spaces or missing items in the combobox).
Code:
Dim rng As Range
Set rng = ActiveSheet.Range("S7:U7")
ActiveSheet.DropDowns.Add(rng.Left, rng.Top, rng.Width, rng.Height).Select
With Selection
.ListFillRange = "AN1:AN" & c
.LinkedCell = "$V$7"
.DropDownLines = 8
.Display3DShading = False
End With
I had a similar problem, i had an ActiveX Listbox in the worksheet. What worked for me is this:
Sheets(1).OLEObjects("ListBox1").ListFillRange = "Sheet2!A1:C20"
ActiveX OLEObjects are pretty straight forward.

Resources