How to clear all slicer only on one sheet? - excel

I'm trying to clear all slicers on a specific worksheet but getting next error:
"object variable or with block variable not set" in this line: cache.ClearManualFilter.
My code:
Sub Clear_all_filters()
Dim cache As SlicerCache
Set mWS = Sheets("Specific_Sheet")
For Each cache In mWS.SlicerCaches
cache.ClearManualFilter
Next cache
End Sub

Just in case someone's still looking for a solution to this question, here is the code I'm using now:
Sub OnePageSlicersReset()
Dim slcrC As SlicerCache
Dim slcr As Slicer
Application.ScreenUpdating = False
For Each slcrC In ActiveWorkbook.SlicerCaches
For Each slcr In slcrC.Slicers
If slcr.Shape.Parent Is ActiveSheet Then
If slcrC.FilterCleared = False Then
slcrC.ClearManualFilter
Exit For
End If
End If
Next slcr
Next slcrC
Application.ScreenUpdating = True
End Sub

Firstly, you should go to Tools>Options and turn on Require Variable Declaration. This will add Option Explicit to the top of any new module (You'll need to add it yourself to any pre-existing module).
This will force you to declare mWS, which would need to be a Worksheet class according to the Set command. This will bring up the error 'Method or data member not found' when you try to run the code.
This is because SlicerCaches are a property in the Workbook class, not Worksheet (As described here: https://learn.microsoft.com/en-us/office/vba/api/excel.slicercache)
In this case we can remove all reference to mWS and just use ThisWorkbook. As this suggests it will loop through all slicers in the current workbook so you may need to do some extra digging if you want to limit it to those in just one sheet.
Option Explicit
Sub Clear_all_filters()
Dim cache As SlicerCache
For Each cache In ThisWorkbook.SlicerCaches
cache.ClearManualFilter
Next cache
End Sub

Related

How to find/run RefreshAll?

I have a simple VBA code:
Private Sub Worksheet_Change(ByVal Target As Range)
Sheet1.RefreshAll
End Sub
This window pops up
Compile error:
Method or data member not found
It directs me to the RefreshAll function.
When I was writing the line, after the fullstop, the drop down list didn't show the RefreshAll function.
RefreshAll is the method for Workbook object. So, you cannot use with Worksheet object. Please see https://learn.microsoft.com/en-us/office/vba/api/excel.workbook.refreshall
As #Adisak said, RefreshAll is a method of Workbook object, not Worksheet object.
So, you need to do one of the following:
1: Refresh all for the whole workbook.
ThisWorkbook.RefreshAll
2: Loop through the objects you want to refresh within the sheet and refresh them one by one. (As mentioned here for pivot tables)
Sub RefreshPT()
Dim wsh As Worksheet
Dim pvt As PivotTable
Set wsh = Sheet1
For Each pvt In wsh.PivotTables
pvt.RefreshTable
Next pvt
End Sub

UserForm_Initialize - Need help troubleshooting Run-time error '91: Object variable or With block variable not set

I can't seem to figure out why my UserForm code fails at the very end of the procedure. The end result is perfect. Here is what I'm trying to accomplish...
1) Using the active workbook, the procedure identifies the sheet names in the workbook and displays them in the Listbox, which is located on the userform.
UserForm Pic
2) Once the sheet is selected, the user will click the "Select Sheet" CommandButton
3) The CommanButton activates the sheet, renames the sheet to "LegacyBillHist"
4) The form closes, and cell A2 is selected
I get the Run-time error 91 message on the very last line.
I've tried a few different approaches to resolve the issue, but I can't seem to figure this one out using the info in StackOverflow and other sites. Any help would be greatly appreciated.
Here is the code..
Option Explicit
Private Sub CommandButton1_Click()
Worksheets(ListBox1.Value).Activate
ActiveSheet.Name = "LegacyBillHist"
Unload BillSelect
End Sub
Public Sub UserForm_Initialize()
Dim wb As Workbook
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Sheets
ListBox1.AddItem sh.Name
Next sh
Load BillSelect
BillSelect.Show
sh.Range("A2").Select
End Sub
Mathieu
Great info. I ended up changing my approach significantly. However, I have a different problem that I haven't been able to figure out. Here is the Code...I got it from the article you referenced...
Option Explicit
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub CommandButton2_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
cancelled = True
Hide
End Sub
Private Sub CommandButton1_Click()
Dim wb As Workbook
Dim ws As Worksheet
Worksheets(ListBox1.Value).Activate
ActiveSheet.Name = "LegacyBillHist"
Set wb = ActiveWorkbook
Set ws = wb.Sheets("LegacyBillHist")
ws.Activate
ws.Range("A1").Select
UserForm1.Hide
End Sub
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim lastSheet As Worksheet
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
UserForm1.Show
End Sub
Everything works great up to the End Sub under the CommandButton1 routine. Instead of hiding UserForm1, it reopens the form.
I tried incorporation the code you provided, but it keeps cycling through until I get the Out of stack space error 28. Perhaps I'm not putting it in the correct sequence.
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim lastSheet As Worksheet
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
With New UserForm1'<~ forms are objects too; avoid using their global state
.Show
End With
End Sub
Appreciate all the help.
For Each sh In wb.Sheets
ListBox1.AddItem sh.Name
Next sh
The object sh only ever makes sense inside the For...Next loop.
In theory, as per language specifications, sh should indeed still hold a reference to the last item in the iterated collection:
When the <for-each-statement> has finished executing, the value of <bound-variable-expression> is the data value of the last element in <collection>.
https://learn.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/MS-VBAL/b132463a-fd25-4143-8fc7-a443930e0651
However it appears when VBA was implemented, that bullet point wasn't: a For Each variable that is Nothing before the loop, will still be Nothing after the loop - and any member call made against Nothing will always raise error 91.
To be honest, I think it's better that way, too: it forces your code to be more explicit:
Dim lastSheet As Worksheet
Dim sh As Worksheet
For Each sh In wb.Worksheets '<~ note: 'Sheets' collection can have charts, not just worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
With New BillSelect '<~ forms are objects too; avoid using their global state
.Show
End With
lastSheet.Activate
lastSheet.Range("A1").Select
Note: Newing-up the BillSelect can surface hidden bugs in your form code, read this article for more info.
The Initialize handler shouldn't be doing this work, especially if you don't New up the form and use its default instance: you do not control when that default instance gets created, VBA does.
Given this code:
UserForm1.Show
If no code ever referred to UserForm1 before, then the Initialize handler runs at UserForm1, before the reference is returned to the caller and the .Show call runs; if the form's default instance isn't destroyed, then the next time the form is shown, the initialize handler will not run again, because the instance is already initialized.
Consider implementing your logic in the Activate handler instead, which will make the logic run after the .Show call is entered, and every time the form is activated (and since that's a modal dialog, that means every time the form is actually shown).

Add EventListener on new sheet vba

I have to create an Excel sheet automatically with vba.
Some cells need an Onchange Event Listener and I wanted to know if there is a way to create this Event Listener automatically by calling a macro instead of writing it down everytime in every sheet code ?
Thank you
I'm going to answer this question because I think it might have some relevance to quite a few other people too, so the code below should get you started in the right direction.
However, there is an expectation on this site that you try to help yourself at least to the same extent as we try to help you. The commenters have mentioned the VBA Object Model, Application Events and AddIns. It shouldn't be beyond the wit of most people to then research these key words (say with a google search). It's not really acceptable simply to say you "dunno where to put the code or what to do", and, in all honesty, doesn't particularly motivate people to help you - put another way, you're pretty lucky to get an answer with that post and comment.
I don't want to get into a huge comment exchange on exactly how to code your specific case. The code here is an example and I would expect you then to research it further. So here goes ...
Insert a class module (research that if you don't know how) and name it - I've called mine cApp. This will enable you to access the Application object and capture its events, like so:
Option Explicit
Private WithEvents mApp As Application
Private mSheetList As Collection
Private Sub Class_Initialize()
Dim ws As Worksheet
'Create instance of the sheet collection
Set mSheetList = New Collection
'If you wanted to add any existing sheets to be checked for changes,
'then you'd do it here.
'Just for an example, I'm using any existing sheets whose name contains "LoP".
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "LoP") > 0 Then
mSheetList.Add ws
End If
Next
'Create instance of Application
Set mApp = Application
End Sub
Private Sub mApp_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim ws As Worksheet
'Test if the changed sheet is in our list.
'Check if the Sh object is a worksheet.
If TypeOf Sh Is Worksheet Then
'Loop through out list of sheets and see if the Sh object is in the list.
For Each ws In mSheetList
If Sh Is ws Then
'Check if the changed range is in the desired range of your sheet.
'In this example, we'll say it has to been in the range "A1:B2".
If Not Intersect(Target, ws.Range("A1:B2")) Is Nothing Then
MsgBox ws.Name & "!" & Target.Address(False, False) & " has changed."
End If
Exit For
End If
Next
End If
End Sub
Private Sub mApp_WorkbookNewSheet(ByVal Wb As Workbook, ByVal Sh As Object)
'A new sheet has been created so add it to our sheet list.
If Wb Is ThisWorkbook Then
If TypeOf Sh Is Worksheet Then
mSheetList.Add Sh
End If
End If
End Sub
You then want to create an instance of this class. I've done it in a standard Module:
Option Explicit
Private oApp As cApp
Public Sub RunMe()
'Create instance of your app class
Set oApp = New cApp
End Sub
You'd then call the RunMe routine somewhere within your code. You might choose to do this in your Workbook_Open() event, but it could be anywhere of your choosing.
I've commented the code pretty heavily so you can see what it's doing and you can always research each of the keywords if you're not sure what they're doing.

Avoiding .Activate with ActiveX Form Control

I'm updating a macro that's used in lots of spreadsheets, and it's rather slow. While looking to speed it up, I noticed that at one point it goes through this loop:
For each wsLoop in ThisWorkbook.Worksheets
wsLoop.Activate
With ActiveSheet.Status_Text
If bStatus = True Then
.ForeColor = &HC000&
.Caption = "ONLINE"
Else
.ForeColor = &HFF&
.Caption = "OFFLINE"
End If
End With
Next wsLoop
Where wsLoop is a worksheet, bStatus is a boolean and Status_Text is the name of an ActiveX label form control on each worksheet.
I know using .Activate is bad practice and can slow things down, so I dropped the wsLoop.Activate and changed the next line to With wsLoop.Status_Text, but now I get a "Method or data member not found" error message.
What's the proper way to do this?
Interesting question which seems to touch on some poorly-documented features of Excel VBA. It seems that maybe the expression ActiveSheet.Status_Text is operating as the name of the control, with the dot acting as a namespace qualifier, but that in wsLoop.Status_Text VBA is interpreting the dot as the method/property access operator and is correctly giving the error message that no such method or property exists. To reproduce the problem, I created a label named Status_Text on each sheet and then ran
Sub test1()
Dim ws As Worksheet
For Each ws In Worksheets
Debug.Print ws.Status_Text.Caption 'fails
Next ws
End Sub
It crashes with the error that you show. One workaround (although just why it works is mysterious) is to change the loop index from being a Worksheet to a Variant:
Sub test2()
Dim ws As Variant
For Each ws In Worksheets
Debug.Print ws.Status_Text.Caption 'succeeds
Next ws
End Sub
The odd thing about this last example is if you add the line Debug.Print TypeName(ws) in the for-each loop it prints Worksheet so ws.Status_Text works if ws is a variant which holds a worksheet but not if ws is actually a worksheet. The mystery is in some sense deepened but in another sense lessened when you step through this sub in the debugger, looking at the locals window. The type of ws in the loop is described as Variant/Object/Sheet1 (in the first pass through the loop). The specific sheet seems to be part of the current subtype of the variable.
Another workaround is to use a For-Next loop rather than a For-Each:
Sub test3()
Dim i As Long
For i = 1 To Worksheets.Count
Debug.Print Worksheets(i).Status_Text.Caption 'succeeds
Next i
End Sub
You could use either of these approaches to get a reference to the label without having to activate the sheet.

Specifying a range to be applied to all subs in a module?

I have been working on creating a module that has multiple subs and functions that all are applied to the same sheet. In my efforts and research to clean up my code I found that instead of declaring the "Dim" for each sub, I can declare it at the very top of the module by using either "Dim" or "Private".
Sub Sample()
Dim DataSheet As Range
'Only declared for this sub, doesn't apply to other subs
'on the other hand,
Private DataSheet As Range
Sub Sample()
'declares it for each sub in this module.
What I can't figure out is, is there a way to set the value or in this case the exact range that I want to assign to "DataSheet" that will apply to the entire module? Currently each of my subs contains,
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
which, since this range is constant and never changes, seems a little redundant.
Create a special sub to perform the initialization and run it first:
Dim DataSheet As Range
Sub RunMeFirst()
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
End Sub
add in a global variable in the ThisWorkbook module and use the workbook open event to set the value.
Public DataSheet As Range
Private Sub Workbook_Open()
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
End Sub

Resources