The following VBA function counts the number of cells containing formulas in a given range. It works correctly when called from a VBA sub. When called from Excel, it returns the total number of cells in the range.
The call from Excel is =CountFormulas(A1:C7), which returns 21 even though only two cells with formulas are in the range.
What is causing this discrepancy?
Public Function CountFormulas(ByRef rng As Range) As Long
CountFormulas = rng.SpecialCells(xlCellTypeFormulas).Count
End Function
Public Sub CountFormulasFromSub()
Dim rng As Range
Dim res As Integer
Set rng = Sheet1.Range("a1:c7")
res = CountFormulas(rng)
End Sub
This isn't possible. The following link has the things that won't work inside of a UDF.
Here - http://support.microsoft.com/kb/170787
EDIT: A manual way of counting works though.
Public Function CountFormulas(rng As Range) As Integer
Dim i As Integer
Dim cell As Range
For Each cell In rng
If cell.HasFormula Then
i = i + 1
End If
Next
CountFormulas = i
End Function
Change Integer to Long if you think it will exceed 32767.
If I were to send worksheet.cells to the function, it would check all cells in the entire worksheet, quite many and quite slow. Although Excel 2007+ supports 16384*1048576 rows, only cells which have actually been used are loaded to memory. There would be no need to go through all the other 17 billion cells to check. The closest I could get to identifying these was using Worksheet.UsedRange to restrict an arbitrary range input. It is not perfect though, in cases when cells far apart have been used. E.g. if cells A1 and XFD1048576 contain data, the entire worksheet would be included in UsedRange. Any tips on how to restrict the range to actually used cells (merely two cells in the above example) would be greatly appreciated.
Utilizing UsedRange I built a function which I'll share in case anyone else can make use of it:
Public Function CountIfFormula(ByRef rng As Range, Optional ByVal matchStr As String) As Long
'Counts the number of cells containing a formula and optionally a specific string (matchStr) in the formula itself.
Dim i As Long
Dim isect As Range
'Restricts the range to used cells (checks only cells in memory)
Set isect = Application.Intersect(rng, rng.Parent.UsedRange)
For Each cell In isect
If cell.HasFormula Then
If InStr(1, cell.Formula, matchStr) Then i = i + 1
End If
Next
CountIfFormula = i
End Function
Use of the function:
Sub GetNrOfCells()
Dim i As Long
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
i = i + CountIfFormula(ws.Cells, "=SUM(")
Next
'i will now contain the number of cells using the SUM function
End Sub
Best regards, and thank you for your reply.
Fossie
Related
I am trying to search through a range for the row matching multiple criteria in Excel VBA. Basically, when I cell is double-clicked, I need to know if the clicked value exists in another sheet, but meeting two additional criteria as well.
I am using the Application.WorksheetFunction method, trying to implement this Excel formula:
=MATCH(1,INDEX((A1=rng2)*(B1=rng3)*(C1=rng4),0,1)
This formula works in the spreadsheet (directly in Excel, not VBA), returning the correct row number:
=MATCH(1,INDEX((TRIM("nasdbpha04")=$D$3:$D$2000)*("Local"=$P$3:$P$2000)*($AQ$1=$AQ$3:$AQ$2000),0,1),0)
When I try to implement the same in the WorkSheet_BeforeDoubleClick event in VBA, the following gives a Run-time error '13' - Type mismatch. My code is below:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim row As Long
Dim dtMax As Date 'corresponds to $AQ$1 above (only put in $AQ$1 to test the formula)
Dim rng1 As Range
Dim rng2 As Range
Dim rng3 As Range
Dim rng4 As Range
Set rng2 = Sheets("DB2 Databases").Range("$d$3:$d$2000")
Set rng3 = Sheets("DB2 Databases").Range("$p$3:$p$2000")
Set rng4 = Sheets("DB2 Databases").Range("$aq$3:$aq$2000")
dtMax = Application.WorksheetFunction.Max(rng3)
row = Application.WorksheetFunction.Match(1, Application.WorksheetFunction.Index((Trim(Target.Value) = rng2) * ("Local" = rng3) * (dtMax = rng4), 0, 1), 0)
End Sub
Comparing variables in the Sub to the formula:
Target.Value = "nasdbpha04" and
dtMax = $AQ$1 in the spreadsheet (but only in the spreadsheet for testing purposes)
I know I could alternately write a function to perform a find command and then check to see if the other two conditions are met on that row, and if not, iterate through until none are left to be found. However, since the formula works in Excel, I figure there should be a way to have it work in VBA using WorksheetFunction.
Any suggestions on what I should correct or do differently would be appreciated.
Thank you.
I have to divide all the cell values of sheet -"Databook" with a number 1000000 only if the cell contains numeric value. It means I have to divide only those cells in the sheet which contains numbers like 17577.2 , 2123, 13979123.22, 239812098321.1, and 9798.
Sub i()
'declare variables
Dim ws As Worksheet
Dim rng As Range
Dim myVal As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("A:Z")
For Each myVal In rng
If IsNumeric(myVal) = True Then
myVal = myVal.Value / 1000000
Else
Next myVal
End Sub
You currently suffer from a missing End If, thus your current code won't run at all. Properly indenting your code would have revealed the issue. I would also recommend not trying to loop all cells in your current range. Note that these are (for Excel 2019 at least) 27.262.976 cells to go through (you might want to first find your range of interest first; last used row, last used column). This many calls will be terribly slow. Limit that numbers by just using the actual numeric values at least.
Try to uitilize SpecialCells. The way it works > <YourRange>.SpecialCells(XlCellType, [XlSpecialCellsValue]). For example:
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
For Each cl In ws.Range("A:Z").SpecialCells(2, 1)
cl.Value = cl.Value / 1000000
Next
End Sub
Where: .SpecialCells(2, 1) can also be read as .SpecialCells(xlCellTypeConstants, xlNumbers)
Note: If you have big chunks of cells that contain numeric values it might be beneficial to loop Areas property instead of Cells since you can load these into an array and perform calculations in memory before pasting back these values. This could also save you some valuable runtime.
I'll start by making my objective clear, and then explaining it fully.
My goal is to check for non-blank values in a range, but only in the hidden cells of that range, and then use conditional formatting in a different cell, depending on whether the cells in the range are empty or not.
I have a named range called Location_Address_RangeCheck that covers the cells directly to the right of the location numbers, like this (location numbers are not part of the range).
When the Number of Locations is changed, the rows that go beyond that number (up to 25) are automatically hidden on worksheet_change to reduce clutter and reduce scrolling to see the stuff below it. That code works fine, so I'm not posting it here so as to not confuse anyone with what I'm trying to accomplish.
I want to provide a safeguard to ensure that there aren't values in the hidden rows that could affect outputs (i.e., if someone selects "3" for Number of Locations, but there is data in cells that might be on the row of the 8th location).
My goal is to check for non-blank values in the range, but only in the hidden cells, and then use conditional formatting in the cell next to the number of locations chosen, depending on whether the cells in the range are empty or not.
So if there is data in the hidden cells, then it would cause the sheet to look like this
.
I've tried so many different things so far, but I'm not making any progress. I've scoured the internet trying to find a solution, but everything I've found is about finding things in visible cells, which is the opposite of what I'm trying to achieve.
Here is the code I have written so far, which I know does not achieve my objective:
Sub testhiddencells()
Dim myRange As Range
Set myRange = Range("Location_Address_RangeCheck")
NumRows = Application.WorksheetFunction.CountA(myRange)
If Range("Location_Address_RangeCheck").Hidden = True Then
If Application.WorksheetFunction.CountA(Range("Location_Address_RangeCheck")) <> 0 Then
MsgBox "There's something there"
End If
End If
End Sub
Here is a minimal example to check with a cell is both hidden and is non-empty:
Option Explicit
Sub Test()
Dim ws As Worksheet
Dim rngToCheck As Range
'test range - all cells populated with 'a' and 3 are hidden
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set rngToCheck = ws.Range("A1:A7")
If TestForNonBlankCellsInHiddenRange(rngToCheck) Then
'do you conditional format stuff here
End If
End Sub
Function TestForNonBlankCellsInHiddenRange(rngToCheck As Range) As Boolean
Dim rngCell As Range
Dim blnCheck As Boolean
'assume that hidden cells are blank
blnCheck = False
'iterate range
For Each rngCell In rngToCheck
If rngCell.EntireRow.Hidden And Not IsEmpty(rngCell.Value) Then
'found a hidden and non-empty cell
blnCheck = True
'debug address of this cell
Debug.Print rngCell.Address
End If
Next rngCell
'return check
TestForNonBlankCellsInHiddenRange = blnCheck
End Function
Looking at the code you used already, you should be able to adapt this to the particular use case of your worksheet.
Can anybody help me out in making all values of my selected range values Absolute (or applying absolute formula on each cell in a range) by single button using Excel VBA?
A B C
5.119999886 -13.06999969 -13.14000034
-5.76999998 -12.52000046 -12.78999996
-5.88000011 -13.69999981 -14.13000011
5.46999979 -12.61999989 -12.48999977
Consider:
Sub dural()
Dim rng As Range
Set rng = Range("A1:F10")
rng.Replace what:="-", lookat:=xlPart, replacement:=""
End Sub
Not exactly VBA, but if required can be recorded.
Select your Data Range, press Ctrl H (Replace), in Find what add " - " sign and replace all. This will remove the negative sign from all numbers and it will become absolute.
Note: As mentioned in comment that OP agrees that this works for him, hence posting as an answer.
You can do something like this:
Sub MakeAbsolute()
Dim c As Range
Dim rngToAbs As Range
'Set the worksheet name and range appropriately
Set rngToAbs = Worksheets("Sheet1").Range("A1:C2")
For Each c In rngToAbs
c.Value = Abs(c.Value)
Next c
End Sub
You need to change Sheet1 to your worksheet name and A1:C2 to whatever range of cells you want to take the absolute value of.
Hopefully the code is simple enough to understand, but the basic idea is that we will declare a specific range, loop through each cell in that range, and change the value of that cell to be the absolute value by applying the Abs function.
I am using an Index/Match to get data from a related table to populate in the first table. In my related table I have filtered out values, but the filtered out values are still populating in my first table. If Index/Match is not smart enough to only grab the filtered values, how can I work around this (formula preferred, but VBA acceptable) to get only the filtered values.
Here is my current formula:
=INDEX(Table_owssvr__1[MyValues],MATCH([#[ID]],Table_owssvr__1[ID],0))
You might find the SUBTOTAL function useful, as it only works on visible rows. (Here's some more general discussion about SUBTOTAL)
But if that's not flexible enough for your needs, here's how to check whether a certain cell is filtered out or not.
Using this, I've written a bit of VBA code to sum over a column summing only visible cells. Should be a pretty useful start in doing whatever you need to do.
If summing over the cells is not what you want to do, just change the part indicated in the comments. (Obviously you'd have to change the name of the function from sumFilteredColumn to something else!)
Public Function sumFilteredColumn(startCell As Range)
Dim lastRow As Long ' the last row of the worksheet which startCell is on
Dim currentCell As Range
Dim runningTotal As Long ' keeps track of the sum so far
lastRow = lastRowOnSheet(startCell)
Set currentCell = startCell
' Loop until the last row of the worksheet
Do While currentCell.Row <= lastRow
' Check currentCell is not hidden
If Not cellIsOnHiddenRow(currentCell) Then
' -------------------------------------------------
' Here's where the magic happens. Change this to
' change sum to, e.g. concatenate or multiply etc.
If IsNumeric(currentCell.Value) Then
runningTotal = runningTotal + currentCell.Value
End If
' -------------------------------------------------
End If
Set currentCell = currentCell.Offset(1) ' Move current cell down
Loop
sumFilteredColumn = runningTotal
End Function
' return the number of the last row in the UsedRange
' of the sheet referenceRange appears in
Public Function lastRowOnSheet(referenceRange As Range) As Long
Dim referenceSheet As Worksheet
Dim referenceUsedRange As Range
Dim usedRangeCellCount As Long
Dim lastCell As Range
Set referenceSheet = referenceRange.Parent
Set referenceUsedRange = referenceSheet.usedRange
usedRangeCellCount = referenceUsedRange.Cells.CountLarge
Set lastCell = referenceUsedRange(usedRangeCellCount)
lastRowOnSheet = lastCell.Row
End Function
' Is the row which referenceCell is on hidden by a filter?
Public Function cellIsOnHiddenRow(referenceCell As Range) As Boolean
Dim referenceSheet As Worksheet
Dim rowNumber As Long
Set referenceSheet = referenceCell.Parent
rowNumber = referenceCell.Row
cellIsOnHiddenRow = referenceSheet.Rows(rowNumber).EntireRow.Hidden
End Function
LondonRob mentioned the SUBTOTAL function. AGGREGATE is a more general function than SUBTOTAL that operates with knowledge of both hidden and filtered cells (there is a difference). They'll do that without addins or VBA, though with somewhat hard-to-read formulae.
I learnt it from here.
I have been able to get this working by the following:
1) Create three worksheets, one for clients, one for purchases, and one for purchasesforclient.
2) Create a Macro to copy filtered values to a new worksheet:
Sub Purchases()
Dim Rng As Range
Set Rng = Worksheets("Comments").Columns("A")
Set Rng = Rng.Resize(65535, 1).Offset(1, 0)
Set Rng = Rng.Resize(, 5).SpecialCells(xlCellTypeVisible)
Rng.Copy Worksheets("PurchasesforClient").Range("A2")
End Sub
3) When I update the purchases via a filter, I run the macro in step 2 by creating a subtotal field and triggering the macro as follows. Since it is a formula, it requires a calculation to occur. This is embedded in the purchases sheet as VBA where the filtering is occurring, where B23 is the subtotal field that changes when it counts the amount of items once a filter is applied:
Public CurrentValue As Double
Private Sub Worksheet_Activate()
CurrentValue = Application.WorksheetFunction.Sum(ActiveSheet.Range("B23"))
End Sub
Private Sub Worksheet_Calculate()
If Application.WorksheetFunction.Sum(Range("B23")) <> CurrentValue Then Purchases
End Sub
4) I use the now filtered values in the purchasesforclient worksheet for my index/match formula in the clients worksheet. This allows me to dynamically filter by date, purchase type, etc. and have updated information in the clients worksheet
This answer requires MOREFUNC addon*
=INDEX(ARRAY.FILTER(Table_owssvr__1[MyValues]),MATCH([#[ID]],ARRAY.FILTER(Table_owssvr__1[ID]),0))
ARRAY.FILTER() function "Stores only the visible cells of a range (for instance a filtered range) in an array and returns this array. "
*MOREFUNC ADDON
Morefunc Addon is a free library of 66 new worksheet functions.
HERE is some information (by original author)
here is the last working download link I found
here is a good installation walk-through video