I have cells in a workbook that link to cells that are named (individually). The cell names all start with "Filter", with some string after that (e.g. FilterSubcategory1").
If the user clicks one of the links, it takes them to the linked cell and then filters another sheet based on where the target was (using .Address at the moment, all works fine). As they all have the same starting string in their name, is it possible to filter if the target name starts with "Filter" instead? This would make my code far shorter rather than listing all relevant named ranges.
It would look something like this (for illustrative purposes, not my full or optimised code):
If Target.Range.Name = "Filter" & "*" Then
'rest of code, do the filtering
End If
or:
If InStr(Target.Range.Name, "Filter") > 0 Then
'rest of code, do the filtering
End If
Please, try the next function:
Function IsFilter(cel As Range) As Boolean
If cel.Hyperlinks.Count = 0 Then Exit Function
If InStr(cel.Hyperlinks.SubAddress, "Filter") > 0 Then IsFilter = True
End Function
The above function can be use in the next way:
Sub testIsFilter()
Dim testCell As Range
Set testCell = ActiveCell 'it may be any cell (resulted from a iteration or not)
If IsFilter(testCell) Then
'rest of code, do the filtering
End If
End Sub
Please, test it and send some feedback.
So, after trying other users' solutions (very helpful in the lead up to the solution), I managed to solve it in a different way. Some hyperlinks linked to themselves (in fact, all the ones that filter do), some linked to merged cells too, so this was the method I used for any cells that linked to themselves:
FltrStr = "='" & ActiveSheet.Name & "'!" & ActiveCell.Address 'builds up the cell address from scratch, allowing for a merged cell being selected
Set FltrRng = Range(FltrStr) 'converts the string into the actual range needed, important for the next line
FltrName = FltrRng.Name.Name 'gets the name of the range, reporting the given name if it exists, lookup .name.name if you are unsure why two .names are needed
I can then use this to check if the target range has a name which starts with whatever I want, here 'Filter':
If InStr(FltrName, "Filter") > 0 Then
'rest of code, do the filtering
End If
Probably not the prettiest/most efficient way to do it, but it works. May be worth defining the types of the variables (FltrStr, FltrRng, FltrName) explicitly at the start of the sub to avoid any type mismatch errors, but I haven't defined them at the moment and they are working fine. (Bad practice, I know!)
Related
I've created a custom function in a module so I can use it in Excel. I've found it works fine except if I go to another tab and enter in some kind of data the function runs from the other tab off the current tab so I get incorrect data.
How do I make it so it will only run if the activesheet contains the custom function? I tried:
If Not ActiveSheet.Name = Sheets(4).Name Then Exit Function
which stops it from running on another sheet, but this is only a bandaid since when you go back to the seat with the function, it shows 0 and you have to run the function again to get the correct data. This also limits it to using it in just 1 sheet. How do I make it so the fuction only uses the current worksheet it's a part of? There has to be a way as normal formulas work like this.
Edit: Here is the code -
Function Indexm(asrng As Range, Mrng As Range, Crng As Range)
For Each cell In asrng
If cell.Value2 Like Mrng.Value2 & "*" Then
If IsEmpty(Indexm) Then
Indexm = Trim(Cells(cell.Row, Crng.Column).Value2)
Else
Indexm = Trim(Cells(cell.Row, Crng.Column).Value2) & ", " & Indexm
End If
End If
Next
End Function
So the asrng is the search index, Mrng is what I'm matching (starting with...), and Crng is the return value I want. The for loop runs through the index and it adds each match to Indexm, seperated by a comma if there's more than 1 match, then finally back to the cell that calls the function.
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
I have a master list of products and stock counts (see image below), but I want for when a product is selected (on another sheet) for the stock count to be subtracted by 1.
Example data set
What is the best way of doing this? (I tired a bit of VBA but didn't get too far)
Edit:
Here is how my mind would picture it working:
How I picture how it would work
Basic VBA Example.
The Worksheet_Change event needs to be on the sheet that contains the dropdown.
Just check to see if the address of the changed cell is the address of your dropdown. You can use a named range or you can just use a static address as I did below.
I gave the list a named range called Items which we can reference.
Please note that on a separate sheet, you would need to use Sheets("SheetName").Range("Items")
I also used a simple For Each loop to check the values in the list - it may be better if it gets large enough to use a dictionary, variant array, or Find.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$H$4" Then
Dim c
For Each c In Range("Items")
If c = Target.Value Then
c.Offset(0, 1).Value = c.Offset(0, 1).Value - 1
Exit For
End If
Next c
End If
End Sub
I'm making a macro that is supposed to check for a set of values in an input list from the user. If the values from my source list are NOT in the user input, they have to be copied into a different spreadsheet. The macro runs without errors, but it won't filter any results. It just copies everything.
This is the code:
Sub CheckRow()
For i = 2 To Application.CountA(Worksheets("Source").Range("A:A")) 'Loop through rows
Set rgFound = Worksheets("Input").Range("A:A").Find(Worksheets("Source").Range("A" & i).Value, LookAt:=xlWhole) 'Find the value from the source list in the Input List
If rgFound Is Nothing Then 'If there is no match it goes to output
Worksheets("Output").Range("A" & Application.CountA(Worksheets("Output").Range("A:A")) + 1).Value = Worksheets("Source").Range("A" & i).Value 'Copy the value beow any existing values
End If
Next i
End Sub
I will appreciate any suggestions you have to offer.
Your condition should be If Not rgFound Is Nothing Then (the Not is missing in your code). Basically, the Find method doesn't find anything and therefore everything is copied.
I also advise you to use Option Explicit at the top of your code sheet. This would alert you to the use of the variable j in Find(Worksheets("Source").Range("A" & j). I think you are looking for A & i. However, since j appears to have a value of 0 your code shouldn't copy anything because there is no row 0. Using Option Explicit would force you to declare all variables, eliminating guessing games like this one when reading your code. Since you are the one to read it most of the time you would also be the prime beneficiary of the improvement.
Fixed. It seems that the Find method thinks that Value and ="Value" are two different things. Once I changed all the formula cells to plain text my code worked like a charm.
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