Detect Autofilter changes - excel

I am looking to detect any changes in an autofilter on a specific table, for the purpose of forcing a UDF that does some simple arithmetic on the table entries that are currently visible to update its result. Making the UDF volatile has had no impact so far, but it doesn't target the table directly either.
This solution (https://stackoverflow.com/a/15906275/4604845) does not work for me, probably because I have only manual calculation.
My a-little-too-dirty workaround is Workbook_SelectionChange, and while the UDF doesn't kill resource consumption this way, I'd rather avoid it in case the data grows larger. Also I have some users that are complete novices in terms of using computers, and I have trouble being confident that I can get all of them to understand that they need to click something else after updating the autofilter for the result to be correct.
Any ideas? I have tried Workbook_Change and Workbook_Calculate but none of them are triggered (or I can't figure out how to trigger them) by the autofilter changes.

Even if you have no other formulas on the worksheet, if you include a Subtotal() formula somewhere on the sheet referencing the table, the Subtotal() will recalculate every time the autofilter is changed.
You can use this to trigger a Calculate() event macro.
EDIT#1:
Say we have an AutoFilter set on column A of a sheet named data. Sheet data also contains many other formulas. If we use the Calculate() event in the data worksheet, we will get fires any time any of these formulas re-calculate.
We create a new worksheet called trigger. This new worksheet is comletely empty except for a single cell that contains:
=SUBTOTAL(3,data!A1:A20)
It is in the trigger worksheet that we place the Calculate() event macro.
Now if we are using the data worksheet, we can make arbitrary changes and perform various recalculation and nothing fires, but if we change the AutoFilter, the event macro on trigger will see the change and fire!

How to trigger Worksheet Calculation following AutoFilter changes while in Manual Calculation
As we know changes to the AutoFilter selection cannot be automatically detected, as these changes do not trigger any Workbook Event or Worksheet Event. Therefore, only option available is to have the user triggering worksheet calculations with an action i.e. Cell Selection Change, Right Click, Double Click, etc; or just by pressing [F9]; which is the preferable action as there is nothing else involved and it's the way it's designed to work.
Nevertheless, at the user request, I'm providing this VBA code that although needs to be initiated by user's action, this action can be done immediately after the AutoFilter change is selected, just by Double Clicking.
The DoubleClick can be unrestricted (double clicking any cell in the worksheet) by using this code:
Private Sub Worksheet_BeforeDoubleClick(ByVal rTrg As Range, blCancel As Boolean)
rTrg.Worksheet.Calculate
End Sub
or setting up to three types of restrictions:
Any cell of the Table
The Header of the Table
The Body of the Table
Use this code to restrict the DoubleClick area:
Currently the code is set to restriction type 1, use variable bRType to change it to the preferred type. This code assumed the name of the Table is Table1 (change as required)
Private Sub Worksheet_BeforeDoubleClick(ByVal rTrg As Range, blCancel As Boolean)
Dim ObjLst As ListObject, rTbl As Range, bRType As Byte
Rem Set Restriction Type
Rem 1: DoubleCliking any cell of the Table - Default
Rem 2: DoubleCliking any cell of the Table Header
Rem 3: DoubleCliking any cell of the Table Body
bRType = 1
With rTrg
Set ObjLst = .Worksheet.ListObjects("Table1")
Select Case bRType
Case 2: Set rTbl = ObjLst.HeaderRowRange
Case 3: Set rTbl = ObjLst.DataBodyRange
Case Else: Set rTbl = ObjLst.Range
End Select
If Not (Intersect(.Cells, rTbl) Is Nothing) Then
.Worksheet.Calculate
blCancel = True
End If
End With
End Sub
Both procedures are Worksheet Events, therefore ensure that the one that you decide to implement goes into the module of the worksheet holding the Table. (do not change the name of the procedures)

Related

Trapping Excel VBA Slicer click events

A similar unanswered question lies here: Excel Macro slicer onclick event
I have a ListObject and a Slicer on that ListObject. The slicer is called "Year". I'm able to trap the on click event with
Private Sub Year_Click()
doCalcsOnFilteredListObject
End Sub
Just right click on the slicer heading > Add Macro
Problem is that I want the trapped event to happen, after the filter selection has been applied. Right now, I trap the event, the filter is never applied, but the calc function runs fine on the (unaltered) listobject.
There are no events for ListObjects and Slicers. The Year_Click you have is an event of container object not of SlicerItem.
The workaround is creating a DUMMY worksheet that has SUBTOTAL Formula on A1 Cell and trap a SheetCalculate event on it. So that when you click on Slicer, it recalculates the formula on DUMMY!A1 after filtering.
Procedures:
Create a new sheet named "DUMMY" & put a formula in A1 cell: "=SUBTOTAL(109, Table1[YEAR])". (Adjust appropriately to your ListObject & Column names.)
Put this VBA procedure.
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
If Sh.Name = "DUMMY" Then
doCalcsOnFilteredListObject
End If
End Sub
Select criteria in Slicer.
This should trigger the desired event and call your custom procedure.
Be aware that Automatic Calculation must be turned on. If you want Manual Calculation, there is a technique using Workbook_Open that sets all sheets into Manual Calculation except the DUMMY sheet.

Combine new dynamic array features of excel with VBA

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.

Automatically set optimal height of visible columns in Excel

I have a fairly large Excel sheet, but I'm only interested in a certain amount of columns at a time. Now some columns contain quite a long text requiring a large cell height. After hiding these columns I wanted to set the cell heights of the visible rows rescaled to the optimum height for better browsing my sheet.
How can these be achieved in Excel either out-of-the-box or with a special rescale VBA Macro?
I'm not an Excel specialist, so any help is welcome her.
You probably have set the cells having long text to "Wrap Text". If you reset this, text is shown in one row now matter how long the content is.
If you do this for all columns that are hidden, Excel is able to calculate the needed height properly:
Sub setheight()
Dim col As Range
For Each col In ActiveSheet.UsedRange.Columns
' Set WrapText to false if column is hidden
col.WrapText = Not col.Hidden
Next
ActiveSheet.UsedRange.EntireRow.AutoFit
End Sub
Unfortunately there is no easy way to trigger this automatically as there is no event that is fired when a column is shown/hidden (yes, there is one, but this is related to the ribbon. If you want to have a look, see Trigger Event in Excel VBA when Row or Column is hidden)
There are numerous ways to trigger the sub, you could for example create a keyboard shortcut to the macro. An alternative that I use sometimes is to create a trigger on DoubleClick on a specific cell or range. I would suggest to run the code when the top row is Double-Clicked. Put the following code into the Worksheet-module
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Target.Row > 1 Then Exit Sub
setheight
Cancel = True
End Sub

Data validation list is being ignored

I have the following situation: on one cell, I have a Data - validation - list which should restrict the user to selecting only items from that list.
On the same cell I have on change triggers in VBA, so when the cell value is changed it also records the change into a Changelog sheet (it uses undo to get the previous value, and undo again to redo the change to the new value)
The problem is, right now, the Data - validation - list is being totally ignored, so the users can put what ever they want in that specific cell, even though I have "Show error" checked.
Is there a way to enforce the list validation, so the users can only select items from the list and not enter whatever they want? Or to trigger the on cell change event after the validation?
Maybe someone can clarify the order in which these things happen.
I recently worked on range.Validation.type=xlValidateList and wanted to prevent paste and cut and disallow typing wrong characters into the cell containing the dropdown list(and making it possible to type characters that match an entry in the list and show that entry).
While working on the last thing that was not OK (cutting a dropdown and pasting it into another cell already containing a dropdown) I discoverd an EASY way of prohibiting paste and cut for Data Validation Lists that do allow further processing depending on what has been entered,
the following code has to be on the worksheet module.
It prevents the user from destroying Data Validation on cells and because of that using Application.Undo to revert to the state before pasting or cutting is no longer needed.
I don't know if it makes a difference but I protected the sheet and unlocked the cells the user may alter.
Private Function HasValidation(ByVal rng As Range) As Boolean
' See: https://superuser.com/questions/870926/restrict-paste-into-dropdown-cells-in-excel
' Returns True if every cell in Range r uses Data Validation
On Error Resume Next
Dim rngType: rngType = rng.Validation.Type
HasValidation = (Err.number = 0)
End Function
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
' Switch DragAndDrop on/off on a range containing DataValidation
Dim modeCutCopy: modeCutCopy = Application.CutCopyMode
Dim usesValidation: usesValidation = HasValidation(Target)
If ((usesValidation = True) And (modeCutCopy <> 0)) Then Application.CutCopyMode = False ' MUST reset to avoid copying / cutting xlValidateList into another xlValidateList
' Turn DragAnddDrop on/off depending on if a cell uses Validation or not
If (usesValidation = Application.CellDragAndDrop) Then Application.CellDragAndDrop = (Not usesValidation)
GoTo SKIP
' Don't allow DragAndDrop if Target intersects with specified ranges
Dim myRange As Range
Set myRange = Worksheets("Evenementen").Range("Oordeel", "Waardering")
Dim rngIntersect As Range
Set rngIntersect = Application.Intersect(Target, myRange)
Application.CellDragAndDrop = (rngIntersect Is Nothing)
'' CheckInputValidationList(Target) ' Sophisticated testing and actions depending on the selection made
'' CheckInputValidationList(Target, myRange) ' Sophisticated testing and actions depending on the selection made for specific ranges
SKIP:
Debug.Print "SelectionChange " & Target.Address & " usesValidation=" & usesValidation & " cellDragAndDrop=" & Application.CellDragAndDrop
End Sub
In this case there are two named ranges containing (different) Data Validation Lists.
As soon as the user clicks on a cell a check will be performed if that cell uses data validation.
If so cut and paste will be turned off and CutCopyMode will be cleared until the user selects another cell.
Important is to retrieve the current CutCopyMode BEFORE any VBA-code will be executed that changes something as VBA will automatically change CutCopyMode from xlCut or xlCopy into 0 when something changes.
That initiall state is required to avoid pasting a dropdownlist over another.
The Worksheet_SelectionChange subroutine contains SKIPPED code that does something similar if a user accesses a cell in one of two ranges.
It also contains the (turned into comment) code to perform additional actions when the user selects a cell. That code might also be put in worksheet_Change

Record values from if Statement to current Cell Excel

I have created and excel spreed sheet.
Its pulls External data from a website into sheet 1.
On Sheet 2 Is where all my calculations are done.
In Sheet 2..
B1 is my Current Value that updates every hour,
M1 Is my current Time,
F1 Is my Current Time,
A4:A27 Is my Date Range,
B3:Y3 Is my Time Range,
And I'm using an if statement.=if(AND(F1=A4:A27), (M1=(B3:y3),B1,"")
If statement works fine. See image Below.
You can see on the 20-11-2017 there is a value under the 7 on today date. When the time changes to 8 the 7 value disappears. As see in the second image below. Because of the if statement not being true on the 7 value any longer.
I'm looking to store the history of the passed values.
How can i allow the if statement to save the values as a value instead of a reference that keeps changing.
You can use the Worksheet_Change Event with the following code. Basically it checks for the cell which is changed, if the cells changed is the "Current Value" cell then it will update the related date / time cell in the table.
Just double check your cell references in code below.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'If the changed cell is the Current Value cell
If Target.Address = "$B$1" Then
Dim LastRow As Long
Dim DateRange As Range
Dim TimeRange As Range
'Can change the sheet name to what ever your final sheet will be called
With Target.Worksheet
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set TimeRange = .Range("B3:Y3").Find(Hour(.Range("K1")))
Set DateRange = .Range("A4:A" & LastRow).Find(.Range("F1"))
.Cells(DateRange.Row, TimeRange.Column).Value = .Range("$B$1").Value
End With
End If
End Sub
What you are looking for is TrackChanges. Excel has a native TrackChanges functions, that when needed, can export the history like you wanted, to a seperated sheet.
The downside is, however, you have to share it. More information can be found here:
Track changes in a Shared Workbook
Important: This article explains an older method of tracking changes using a "Shared Workbook." The Shared Workbook feature has many limitations and has been replaced by co-authoring. Co-authoring doesn't provide the ability to track changes. However, if you and others have the file open at the same time, you can see each other's selections and changes as they happen. Also, if the file is stored on the cloud, it's possible to view past versions so you can see each person's changes. Learn more about co-authoring.
If you want to go with VBA route, you can make a sub to copy every newly data added to the worksheet whenever it changes ( event-trigger sub )
Private Sub Worksheet_Change(ByVal Target As Range)
' Do stuff when worksheet changes
End Sub
An example is this: How do I get the old value of a changed cell in Excel VBA?

Resources