Forum

User Defined Functi...
 
Notifications
Clear all

User Defined Function to determine if range is filtered - NOT working

16 Posts
8 Users
0 Reactions
481 Views
(@Anonymous)
Posts: 0
New Member Guest
 

In a current application I have a sheet with multiple tables (NOT Excel structured type - just basic matrices) down the page - and need to use Autolfilters on different tables from time to time (Using XL2007-2010 can only have one set of Autolfilters at a time). In addition, as part of a report printing routine, I need to change the table description to more appropriately describe the report containing the filtered result (e.g. "Obsolete Stock" as a subset of all stocks).

I have managed to achieve this by using a formula that compares the number of spreadsheet rows in the table with the number of text values counted in the filtered range referencing the same spreadsheet rows, viz;

=IF( ROWS(REF1:REF2)<>SUBTOTAL(3,REF1:REF2), "Filtered list description", Table description")

However, some of the tables don't have a range that has all text values [so SUBTOTAL(3,REF1:REF2) won't work], and the numeric columns may contain blanks! So, I got to thinking that a simple UDF could be created to determine if the target range was filtered or not, and drafted the following code:

Public Function FilterOn(Rng As Range) As Boolean

    Dim RngVis As Range
    Dim c1 As Long
    Dim c2 As Long

    Application.Volatile

    c1 = Rng.Cells.Count

    Set RngVis = Rng.SpecialCells(xlCellTypeVisible)
    c2 = RngVis.Cells.Count

    If c1 = c2 Then
        FilterOn = False
    Else
        FilterOn = True
    End If

End Function

My formula would then become =IF( FilterOn(REF1:REF2), "Filtered list description", "Table description").

To my dismay, the code doesn't work, with the same result being returned whether the range is filtered or not (i.e. c2 = RngVis.Cells.Count returns the same number as c1 = Rng.Cells.Count even though some of the cells in the range are filtered out and therefore not visible). This would imply that the code lines shown in red are not doing what I expect them to do.

Anyone got any ideas as to why this is not working, and what modifications are required to rectify the situation?

 

Cheers

Col

 
Posted : 25/06/2016 8:35 am
(@jstephens)
Posts: 5
Active Member
 

Not 100% following your issue (sometimes its hard to explain just in words I know).  But one thing I can tell you is that your code "Rng.Cells.Count" will always give you the same results no matter if there is no records in the cells, all cells filled in, filtered or not filtered.  That result is 1048576, which of course is the maximum number of rows in excel.  You can test this by simply adding MsgBox c1 & MsgBox c2 to your code.  You'll see that this is the answer.

I'm hoping that this can help you.  My assumption:  you are trying to determine the number of rows in each table & you are not having any issues with your code filtering out the desired results.  Change c1 to:

c1 = Application.CountA(Range("A:A")) - of course column A could be changed to any column you want to count. 

Change c2 to:

c2 = Range("A" & Rows.Count).End(xlUp).Row - again, change A to what ever column you want to count.

Again, if you add the MsgBox for c1 & c2, you will see that you get different results.

Hopefully that works!

 
Posted : 25/06/2016 10:42 am
(@roy_cox)
Posts: 1
New Member
 

I have used this function in the past

Option Explicit
'---------------------------------------------------------------------------------------
' Procedure : TestFiltered
' DateTime : 10/10/2005 18:50
' Author : royUK
' website :
' Purpose : Check if data is Filtered
'---------------------------------------------------------------------------------------
'
Function TestFiltered() As Boolean
Dim rngFilter As Range
Dim r As Long, f As Long
Set rngFilter = ActiveSheet.AutoFilter.Range
r = rngFilter.Rows.Count
f = rngFilter.SpecialCells(xlCellTypeVisible).Count
If r > f Then TestFiltered = True
End Function
Sub TestFunction()
MsgBox TestFiltered
End Sub

 
Posted : 25/06/2016 12:13 pm
(@adder)
Posts: 1
New Member
 

in your formula: =IF( ROWS(REF1:REF2)<>SUBTOTAL(3,REF1:REF2), "Filtered list description", "Table description")

I don't get the red part... why use "ROWS" function? The "COUNTA" function should work (I tested in Excel 2010)

=IF( COUNTA(REF1:REF2)<>SUBTOTAL(3,REF1:REF2), "Filtered list description", "Table description")

if you have any hidden rows (not by filter), you can always use 103 instead of 3 in the "SUBTOTAL" function.

 
Posted : 25/06/2016 1:27 pm
(@dangelor)
Posts: 1
New Member
 

I think the FilterMode property would give you the info you need...

Dim x As Boolean
x = ActiveSheet.FilterMode
MsgBox x

This would return True if any filters were applied in a worksheet.

 
Posted : 26/06/2016 8:00 pm
(@atsf)
Posts: 3
New Member
 

dangelor said
I think the FilterMode property would give you the info you need...

Dim x As Boolean
x = ActiveSheet.FilterMode
MsgBox x

This would return True if any filters were applied in a worksheet.  

For your info, this nice code works in 2016 if the cursor would be placed within the table.

 
Posted : 27/06/2016 2:01 pm
(@Anonymous)
Posts: 0
New Member Guest
 

Thanks for the responses, to which I advise as follows:

James: The variable Rng in the UDF is a user specified range (e.g. "A1:A10") rather than the whole sheet. The purpose of "c1 = Rng.Cells.Count" is to return the actual number of rows in the specified range (without any hidden or filtered rows) to provide the benchmark against which the number of VISIBLE rows in a filtered list will be assessed to determine if a filter has been applied to that range.

Adder: Same answer as for James above (i.e. in my formula based work-around, ROWS(REF1:REF2) is used to return the actual number of rows in the specified range (without any hidden or filtered rows) to provide the benchmark against which the number of VISIBLE rows in a filtered list will be assessed to determine if a filter has been applied to that range.

Dangelor: x = ActiveSheet.FilterMode will only tell me that a filter has been applied somewhere on the target worksheet, which may not be to my target range. As I have a number of different tables/ranges that could have an autofilter applied at different times, I need to know whether or not the target range has autofilters applied and is filtered.

Roy: You're in the same territory, but I get the same (incorrect) result.Frown

Note that "r = rngFilter.Rows.Count" counts the number of rows of the range to which the autolfilter is applied (the benchmark) whereas "f = rngFilter.SpecialCells(xlCellTypeVisible).Count" should return the number of visible cells - which for a multi-column filter range obviously doesn't work. Nevertheless, it doesn't seem to work in a single-column filter range either!CryTry it on a single column range, say of 10 rows total including header, with all cells containing unique values. Then filter out two values. r correctly returns 10 but f also returns 10 (incorrectly!) instead of 8. Go figure? It seems to me that the problem lies with rngFilter.SpecialCells(xlCellTypeVisible).Count not correctly returning the count of VISIBLE cells only!

 
Posted : 27/06/2016 10:46 pm
(@catalinb)
Posts: 1937
Member Admin
 

Hi Colin,

Roy was very close, that is the correct approach, the only error was to compare rows with cells, we always should compare the same type of ranges.

The best way to achieve your desired result is to count the cells (or the rows) from the first column of the AutoFilter range, this way it will work with any dimension of the filtered range, it may have 1 column, or 100 columns, we check only the first.

Function TestFiltered() As Boolean
Dim rngFilter As Range, r As Long, f As Long
Set rngFilter = ActiveSheet.AutoFilter.Range
r = rngFilter.Columns(1).Cells.Count
f = rngFilter.Columns(1).Cells.SpecialCells(xlCellTypeVisible).Count
TestFiltered = (r > f)
End Function

Or, by counting rows from the first column, it does not matter, it's the same thing:

Function TestFiltered() As Boolean
Dim rngFilter As Range, r As Long, f As Long
Set rngFilter = ActiveSheet.AutoFilter.Range
r = rngFilter.Columns(1).Rows.Count
f = rngFilter.Columns(1).Rows.SpecialCells(xlCellTypeVisible).Count
TestFiltered = (r > f)
End Function

If you test only the first column, you can even compare cells.count with rows count, the result will be the same ( in the first column, each row will always have only 1 cell):

r = rngFilter.Columns(1).Rows.Count
f = rngFilter.Columns(1).Cells.SpecialCells(xlCellTypeVisible).Count

Catalin

 
Posted : 28/06/2016 12:26 am
(@Anonymous)
Posts: 0
New Member Guest
 

Hi Catalin

I understand what both you and Roy are saying - but it is NOT working in my application!Yell

I tested this code (using rows, cells, and a combination thereof as the object) on a simple 32 row x 3 column range. Column 1 has text values in every cell.

With Autofilter on but no filter criteria applied (i.e. ActiveSheet.FilterMode = False): r = 32 (correct), f = 32 (correct)

With Autofilter on & filter criteria applied to exclude several items (i.e. ActiveSheet.FilterMode = False):  r = 32 (correct), f = 32 (incorrect!)

As I said before, it seems to me that the problem lies with rngFilter.SpecialCells(xlCellTypeVisible).Count not correctly returning the count of VISIBLE cells only!

This may not be helpful, but rngFilter.Columns(1).Rows.SpecialCells(xlCellTypeVisible).Address returns exactly the same address as the Autofiltered range, whereas I was expecting it to return an array of individual cell addresses representing the visible cells.

 
Posted : 28/06/2016 5:00 am
(@catalinb)
Posts: 1937
Member Admin
 

Can you provide more details? What version of excel are you using?

Also, a sample file will say more than any words, please upload a sample file. (always prepare sample files with new topics, it's the best way to make yourself understood and you will receive more accurate answers)

Cheers,

Catalin

 
Posted : 28/06/2016 5:09 am
(@Anonymous)
Posts: 0
New Member Guest
 

Sample file attached - created in Excel 2007.

Set one or both of the filters to see the results

 
Posted : 28/06/2016 10:03 am
(@catalinb)
Posts: 1937
Member Admin
 

Hi Colin,

The problem is related to what an UDF can and cannot do, sorry I did not see that from the beginning, I was aware of this UDF limitation.

By default, a User Defined Function cannot make changes to any cell, it will affect the calculation tree. More details here: Excel Recalculation

A worksheet function that changes the structure of the worksheet by, for example, copy -pasting a range of cells will fail. Excel will simply ignore the code lines that are attampting to change cells, so the reason for the failure is not obvious.

The Autofilter method is in the black list, as it will make changes to the values of the cells, it will simply not work.

If you try this code, you will see that the code works in a normal procedure, but not in a UDF:

Sub TestFiltered_Combo()
Dim rngFilter As Range, r As Long, f As Long

Application.Volatile

Set rngFilter = ActiveSheet.AutoFilter.Range

r = rngFilter.Columns(1).Rows.Count
f = rngFilter.Columns(1).Cells.SpecialCells(xlCellTypeVisible).Count

MsgBox (r > f)
End Sub

For a UDF, we have to use a different approach, by evaluating other cell properties, like RowHeight, or EntireRow.Hidden, like in the example below:

Function Test2(rng As Range) As Boolean
Test2 = False
Dim cell As Range
For Each cell In rng
    If cell.EntireRow.Hidden Then Test2 = True: Exit For
Next
End Function

For large ranges, the code will exit at the first hidden row, there is no need to evaluate all remaining cells.

You can also use worksheet functions in VBA, to calculate a count of visible rows:

Test=Evaluate("= ROWS($B$1:$B$9)<>SUBTOTAL(3,$B$1:$B$9)")

Or:

Application.WorksheetFunction.Subtotal(3,Rng)

Let us know if this solves your problem.

Catalin

 
Posted : 28/06/2016 11:59 am
(@Anonymous)
Posts: 0
New Member Guest
 

Catalin: Thanks for the explanation - an interesting/intriguing snippet that is not readily apparent (I've never seen anything written about limitations of UDFs!)

 

I will now review my code to see how I can adapt it to accommodate this limitation - though I want to make it a generic function that can be used on any range.

 

Cheers

Col

 
Posted : 28/06/2016 8:27 pm
(@Anonymous)
Posts: 0
New Member Guest
 

Catalin: Your function using the For Next Loop, viz;

Function Test2(rng As Range) As Boolean
 Test2 = False
 Dim cell As Range
 For Each cell In rng
     If cell.EntireRow.Hidden Then Test2 = True: Exit For
 Next
 End Function

seems to work, and is the simplest, so I'll use that code.

I was fixated on using SpecialCells(xlCellTypeVisible) as I understood it was specifically designed for doing what I wanted (which I guess it does - it's just the limitation of using Autofilter in a UDF that's the real problem.)

Many thanks

 
Posted : 30/06/2016 2:32 am
(@catalinb)
Posts: 1937
Member Admin
 

Great, glad to hear that your problem is solved.

Just make sure that you don't pass entire columns to the function, if there is no filter applied, the function will loop through all cells from the range indicated.

Also, you should check only the first column from the range, otherwise the function will loop through each cell, even if they are in the same row. Cells from the same row will always have the same height. Passing a range like Sheet1!A:C will start a loop through over 3 milion cells... If you use the function in 10 cells, this means 30 milion cells to be evaluated 🙂

There is an exit in the function, but if no rows are hidden, the function will not stop until it will evaluate all cells.

Function Test2(rng As Range) As Boolean
Test2 = False
Dim cell As Range
For Each cell In rng.Columns(1)
    If cell.EntireRow.Hidden Then Test2 = True: Exit Function
Next
End Function
 
Posted : 30/06/2016 3:25 am
Page 1 / 2
Share: