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
Related
I tried to work a bit more with dynamic arrays in excel in combination with vba. My problem is that I cant return a table-column with vba. Here a minimal example of what I want to do:
I have two Tables TabFeb and TabMar (see image below). Each of them has a column costs which I want to sum up individually. The results shall be put into a new Table. This can be easily done in excel with =SUM(TabFeb[Costs]) and =SUM(TabMar[Costs]), respectively. My idea is now to write a VBA function which takes a string as input, in this example it will be the month, and returns the table acording to the input. After that it will be summed up and the result is given in a cell.
I tried the following:
Function Selectmon(mon As String) As Range
If mon = "Feb" Then
Set Selectmon = Worksheets("Sheet1").ListObjects("TabFeb").ListColumns("Costs").DataBodyRange
ElseIf mon = "Mar" Then
Set Selectmon = Worksheets("Sheet1").ListObjects("TabMar").ListColumns("Costs").DataBodyRange
End If
End Function
The problem of this idea is that this function just copy the table data. Hence, if I would change the input table data the sum would not change. One has to recalculate every cell by hand. Somehow I need VBA to return TabFeb[Costs] for the input "Feb". Does anyone have an idea how this can be done?
Example
It's really just a one-liner (unless you want to do some in-function error checking)
Function Selectmon(mon As String) As Range
Set Selectmon = Range("Tab" & mon & "[Costs]")
End Function
As implied by #ceci, this formula will not update with changes in the table. Depending on other particulars of your worksheet, you can have it update either by
embedding it in a worksheet change event code;
or by adding the line Application.Volatile to the function itself.
The latter method will force a recalculation when anything changes on the worksheet that might cause a recalculation.
The first method can limit the recalculation only when there has been a change in the data, but has other limitations.
One of the limitations of the Worksheet Change method is that it will only work on the relevant worksheet.
If you use the Workbook sheet change method, you won't have that limitation.
In either event you can limit your code to run only when the table has changed.
Here is one generalized method:
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim LOS As ListObjects
Dim LO As ListObject
Set LOS = Sh.ListObjects
For Each LO In LOS
'could select relevant tables here
'Could also select relevant worksheets, if you like
'for example
Select Case LO.Name
Case "TabFeb", "TabMar", "TabApr"
If Not Intersect(LO.DataBodyRange, Target) Is Nothing Then
Application.EnableEvents = False
Application.Calculate
End If
End Select
Next LO
Application.EnableEvents = True
End Sub
And there is other code you could use to find the relevant formula and just update that formula -- probably not worth the effort.
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.
Data validation in Excel is a helpful way to verify user input in Excel. The standard way is to (1) define somewhere (e.g. on an auxillary sheet) a list with possible input values and (2) then choose that range in the Source field. Alternatively, one can also directly type in the different options in that field, e.g:
My question: How do we make the data-validation list dynamic?
What I tried so far is to enter a (possibly user-defined VBA) formula returning a list of strings in into the field Source of the data-validation dialog, for example
=INDEX({"New","Mint","Very Good","Good","Acceptable","Poor"},{1,RANDBETWEEN(1,6)})
However, this approach does not work as it leads to an error message
You may not use reference operators (such as unions, intersections,
and ranges) or array constants for Data Validation criteria.
What do I miss? Which (possibly more elegant) way of making the Source of the data validation dynamic do you suggest?
Edit: My concrete problem:
On all (but the first) tab sheet, I have a cell called myTest and the list of allowed values is for a cell on the first tab, where I want be able from the list composed of all possible values of myTest.
References:
Debra Dalgleish: "Create Dependent Drop Down Lists" at contextures.com
Dynamic Data Validation in Excel (Non-VBA!)
You can use something like this in the first sheet's code module:
Private Sub Worksheet_Activate()
Const LIST_COL As Long = 26
Dim sht As Worksheet, i As Long
i = 1
Me.Columns(LIST_COL).ClearContents '<< clear current list
For Each sht In ThisWorkbook.Worksheets '<< collect all the values
If sht.Name <> Me.Name Then
Me.Cells(i, LIST_COL).Value = sht.Range("myTest").Value
i = i + 1
End If
Next sht
End Sub
It will collect the values from the other sheets into a range you can reference for the validation list.
Note: if the other sheet's values are updated via code or formulas (ie. do not require you to go to each sheet to change values) then you'd need to do a bit more work.
EDIT - this is more convoluted but works more reliably, since it gets run whenever you click on the validation drop-down
1. Put this in a regular module (edit as required):
'A function to return a range containing the
' various values which need to appear in the validation list
Public Function ListCompile() As Range
Const LIST_COL As Long = 26 '<< create the list in Col Z
Dim sht As Worksheet, i As Long
i = 1
Sheet1.Columns(LIST_COL).ClearContents
For Each sht In ThisWorkbook.Worksheets
If sht.Name <> Sheet1.Name Then
Sheet1.Cells(i, LIST_COL).Value = sht.Range("myTest").Value
i = i + 1
End If
Next sht
'return the list we just created
Set ListCompile = Sheet1.Cells(1, LIST_COL).Resize(i, 1)
End Function
Note: I'm using the codename for Sheet1 (which might be different from the tab name). You can see the code name in the VB editor Project Explorer.
2. Define a named range "tester" with "RefersTo" equal to ListCompile()
3. Finally set your data validation list range to: =tester
Assuming:
Your list is in column A
There is no header
You could make data-validation with a dynamic list using this formula as source:
=OFFSET(A1,0,0,COUNTA(A:A),1)
Edit: below is an image of an example using the formula above in data-validation applied to cell B1:
If you can somehow join your values in a single column, this is a possibility.
I have a UserForm with text boxes that are already bound to certain worksheet cells through the ControlSource property. I need to run a calculation between two of these bound values and have the result end up in a third worksheet cell. I know there are numerous ways this can be done, but I'm wondering whether there's some way to do this as a formula in the worksheet cell that references the UserForm control values. For example, I would like to be able to put a formula in cell C3 that goes something like
= UserForm1.TextBox1.Value * UserForm1.TextBox2.Value
but I haven't found any reference that addresses using worksheet cell formulas to fetch values directly from UserForm controls. (And no, in this case I can't just reference the bound cells by plugging something like "= A1 * B2" into cell C3. This question is specifically about whether it's feasible to reference a UserForm control from within a worksheet cell formula.)
Thanks in advance for any helpful suggestions.
The only way to refer to the property of an ActiveX control on a worksheet, is via a user-defined-function, such as:
Public Function GetTextBoxValue(TextBoxName As String) As String
On Error GoTo 0
Dim o As OLEObject
Set o = Sheet1.OLEObjects(TextBoxName)
On Error Resume Next
If Not o Is Nothing Then
GetTextBoxValue = o.Object.Text
End If
End Function
Then call the function from a cell, like: =GetTextBoxValue("TextBox1")
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.