VBA MsgBox After Background Query Completion? - excel

I have created a VBA code to import data from CSV convert it into a table and refresh the query that is already setup.
I want the user to be informed when the background query is completed by displaying a VBA msgbox.
I tried below code but it doesn't work because if condition would be nothing by the the time query is completed. So no msgbox will be display.
Do I need to setup some delay like 15 sec and display msgbox anyway but then it wouldn't be a good idea.
How to sync background query completion with VBA msgbox?
ThisWorkbook.RefreshAll
Sheets(2).Select
If Sheets(2).Range("AG3").Value <> "" Then MsgBox "Completed"

The Delay is tricky. Sometimes it could be 2 seconds or 10 to update the whole data, and also when the data gets bigger, the system will need more time to update the Data Model. This means that the "MsgBox" could appear when the data still updating.
I understand the perks of the Background Update, but it is important to know that it stops when you save the workbook. Instead, I would block any activity in the workbook until the Data Model is complete updated. For this, I use the following code that I found here long time ago:
Sub Aktualisieren()
On Error Resume Next
Application.DisplayAlerts = False
Application.ScreenUpdating = False
With ThisWorkbook
For Each objConnection In .Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.Refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
'Save the updated Data
.Save
End With
Application.DisplayAlerts = True
Application.ScreenUpdating = True
MsgBox "Data Model Updated"
End Sub
Now, as you ask, if you want to update one specific Query, first you need to know the specific name of it (it is not always the same as you have written). For this run the following code:
Sub Get_Conection_Names()
With ThisWorkbook
'Check if there is any conection
If .Connections.Count = 0 Then Exit Sub
'Print the numer of conection (item number) and its name
For X = 1 To .Connections.Count
Debug.Print X & ": " & .Connections.Item(X).Name
Next X
End With
End Sub
With the information of the Query, you can add to a bottom the following code:
Sub UpdateConectionbyName()
Dim ConectionName As String
ConectionName = "Query - Name of Query"
With ThisWorkbook
'Check if there is any conection
If .Connections.Count = 0 Then Exit Sub
'Check the connections names if one macht with the one we want.
For X = 1 To .Connections.Count
If .Connections.Item(X).Name = ConectionName Then .Connections.Item(X).Refresh
Next X
End With
End Sub
And now, if you want to Refresh a specific Query unabeliing the Background Update, this code will help. Item 1 is the number of the query. You can get it with the code above.
With ThisWorkbook.Connections
bBackground = .Item(1).OLEDBConnection.BackgroundQuery
.Item(1).OLEDBConnection.BackgroundQuery = False
.Item(1).Refresh
.Item(1).OLEDBConnection.BackgroundQuery = bBackground
End With

Related

Delete Worksheets based on Checkbox

I am currently trying to write a piece of code where someone is able to use a checkbox to choose which worksheets they would like to keep and what they would like removed. Here is what that looks like:
(currently debating if I should turn this into a userform but i would still be stuck at this point).
What I would like to do is if the checkbox is unchecked (false) on the worksheet called "Setup", delete the worksheet and move onto the next if statement. From the code below, I am prompt with the run-time error '1004': Unable to get the OLEObjects property of the worksheet class. I have checked and the Checkbox name is the same as what I have in my code.
Sub DeleteSheetCB()
If ThisWorkbook.Worksheets("Setup").OLEObjects("CheckBox1") = False Then
ThisWorkbook.Worksheets("Program Information").Delete
End If
If ThisWorkbook.Worksheets("Setup").OLEObjects("CheckBox2") = False Then
ThisWorkbook.Worksheets("Spend and Trx Data").Delete
End If
If ThisWorkbook.Worksheets("Setup").OLEObjects("CheckBox3") = False Then
ThisWorkbook.Worksheets("Requirements").Delete
End If
If ThisWorkbook.Worksheets("Setup").OLEObjects("CheckBox4") = False Then
ThisWorkbook.Worksheets("TMC Overview").Delete
End If
End Sub
Thank you in advance
EDIT:
I was able to get this piece of code to delete sheets but if possible, would someone be able to sense check this for me please?
Sub DeleteSheetCB()
If ThisWorkbook.Worksheets("Setup").Shapes("Check Box 1").ControlFormat.Value <> 1 Then
ThisWorkbook.Worksheets("Program Information").Delete
Else: End If
If ThisWorkbook.Worksheets("Setup").Shapes("Check Box 2").ControlFormat.Value <> 1 Then
ThisWorkbook.Worksheets("Spend and Trx Data").Delete
Else: End If
If ThisWorkbook.Worksheets("Setup").Shapes("Check Box 3").ControlFormat.Value <> 1 Then
ThisWorkbook.Worksheets("Requirements").Delete
Else: End If
If ThisWorkbook.Worksheets("Setup").Shapes("Check Box 4").ControlFormat.Value <> 1 Then
ThisWorkbook.Worksheets("TMC Overview").Delete
Else: End If
End Sub
The main thing I'd take from your second code is:
It will give you a warning before it deletes each sheet
You'll get a subscript out of range error if the sheet has already been deleted.
You have to update your code if you add a new tick box.
The code below assumes the caption of the checkbox is exactly the same as the name of the sheet to be deleted.
Sub DeleteSheetCB()
Dim chkBox As CheckBox
Dim sMissing As String
With ThisWorkbook.Worksheets("Setup")
For Each chkBox In .CheckBoxes 'Look at all checkboxes in Setup sheet.
If chkBox.Value = 1 Then 'If it's ticked.
If WorksheetExists(chkBox.Caption) Then 'Check worksheet exists.
Application.DisplayAlerts = False 'Turn off warnings about deleting a sheet.
ThisWorkbook.Worksheets(chkBox.Caption).Delete
Application.DisplayAlerts = True 'Turn on warnings about deleting a sheet.
Else
sMissing = sMissing & "- " & chkBox.Caption & vbCr
End If
End If
Next chkBox
End With
If sMissing <> "" Then
MsgBox "These sheet(s) could not be deleted as they were already missing: " & vbCr & vbCr & sMissing
End If
End Sub
Public Function WorksheetExists(SheetName As String) As Boolean
Dim wrkSht As Worksheet
On Error Resume Next
Set wrkSht = ThisWorkbook.Worksheets(SheetName) 'Try and set a reference to the sheet.
WorksheetExists = (Err.Number = 0) 'Was an error thrown?
On Error GoTo 0
End Function
Might also be worth mentioning that you can rename your checkboxes:
Select a check box so the Shape Format ribbon becomes visible.
Click Selection Pane under the Arrange section.
A sidebar will appear showing the shapes on the sheet. You can rename or change their visibility here.
chkRemoveProgramInfo makes more sense than Check Box 1.

Excel gets corrupt and goes into read only mode

So I have an excel document that will just randomly break on me when opening it. I do have Code 1 in the VBA ThisWorkbook section but it doesn't start until I enable macros after opening it.
The images attached are in the order that they appear to me. One note is that I do have hidden files revealed and I only see the "Ownership file" when I have it open. I am on a shared network but I do not have the privileges to view where it all is open.
My current work around is to save the file under a different name and then delete the old file and rename it.
After researching a bit, someone stated it might have had to do with sorting. But I added Code 2 and I am still having the issue.
Code 1
Dim Result
Result = MsgBox("The Data in this document might be outdated. Would you like to refresh the Data Queries? This process could take a few minutes...", vbYesNo, "Data Query OutDated")
If Result = vbNo Then
Exit Sub
End If
MsgBox "Queries Will Refresh Upon Closing this window. Please wait"
ActiveWorkbook.Worksheets("SQLData").EnableCalculation = False
ActiveWorkbook.Worksheets("FlowBreakDown").EnableCalculation = False
Application.ScreenUpdating = False
Change_Background_Refresh False
ActiveWorkbook.RefreshAll
Application.ScreenUpdating = True
ActiveWorkbook.Worksheets("SQLData").EnableCalculation = True
ActiveWorkbook.Worksheets("SQLData").EnableCalculation = False
ActiveWorkbook.Worksheets("FlowBreakDown").EnableCalculation = False
MsgBox "Refresh Complete"
Call ResizeData
End Sub
Code 2
Dim Sht As Worksheet
' Clear all Sort Fields prior to Save & Exit
For Each Sht In Application.Worksheets
Sht.Sort.SortFields.Clear
Next Sht
End Sub

Excel Vba - Event Macro Crashes on 2nd Run when used in "Worksheet Events". works perfectly in break mode

I'm going through a peculiar failure in my excel vba macro wherein the excel sheet crashes on the 2nd run of the macro (Yes, 1st run in perfect).
This macro is to delete all data labels except the recent one for a particular series in all graphs in a sheet. Common graph is used with slicer and Data table to select which line item is needed in graph. on every change in slicer (new line items is selected in data table filter), i want the macro to run and do the label work.
Below is source code for the Graph data label work,
Sub Update_labels_Only_To_Last_Data()
Dim myChartObject As ChartObject
Dim mySrs As Series
Dim myPts As Points
Dim iPts As Long
Dim bLabeled As Boolean
Application.EnableEvents = False
Name = ThisWorkbook.Name
Set Wb = Workbooks(Name)
Set WsGraph = Wb.Worksheets("Graph")
WsGraph.Activate
With ActiveSheet
For Each myChartObject In .ChartObjects
For Each mySrs In myChartObject.Chart.SeriesCollection
If mySrs.Name = "TAR" Then
bLabeled = False
With mySrs
For iPts = .Points.Count To 1 Step -1
If bLabeled Then
' handle error if point isn't plotted
On Error Resume Next
' remove existing label if it's not the last point
mySrs.Points(iPts).HasDataLabel = False
On Error GoTo 0
Else
' handle error if point isn't plotted
On Error Resume Next
' add label
mySrs.Points(iPts).ApplyDataLabels _
ShowSeriesName:=False, _
ShowCategoryName:=False, ShowValue:=True, _
AutoText:=True, LegendKey:=False
bLabeled = (Err.Number = 0)
On Error GoTo 0
End If
Next
End With
Set myPts = mySrs.Points
myPts(myPts.Count).ApplyDataLabels Type:=xlShowValue 'Add only to last data
End If
Next
Next
End With
'End If
Application.EnableEvents = True
End Sub
I've tried running macro using a separate button it works perfectly. Then linked this with event macro (Calculation) using below code in sheet (Donotdelete)
Option Explicit
Private Sub Worksheet_Calculate()
Dim Act_Sheet As Worksheet
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculate
If Not Application.CalculationState = xlDone Then 'wait till previous calculation gets over
DoEvents
End If
Set Act_Sheet = ActiveSheet
Application.Wait (Now + TimeValue("0:00:01"))
Call Update_labels_Only_To_Last_Data
Act_Sheet.Activate
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
so once a slicer filter is changed, calculate event is triggered using "Count" function in "Donotdelete" sheet and macro gets run. when first time slicer us changed, macro works perfectly. but when 2nd time the slicer is changed to call different graph, macro gets initiated and all of a sudden, completely sheet gets crashed/closed. Tried in break mode and the code works perfectly. tried giving doevents / wait options and still the result is same. To confirm the crash source, tried skipping the "macro" using comment block in eventmacro sheet & the code works perfectly. on any selection change in slicer, macro goes thro the event macro and works perfectly. with this, source is confirmed to be the macro "Update_labels_Only_To_Last_Data()".
i use the same macro in main module and there it works perfectly any number of consecutive times. could not find why it crashes only when used through even trigger and only on the 2nd time !!!. did all my google help thing for couple of weeks now but no result. see if you guys can show me a wayout.

Restrict viewing access to an Excel worksheet

I thought this would be a readily used function in Excel but it's surprisingly difficult to implement a simple process of restricting access to specific worksheets within a larger workbook.
There's a few methods that prompt an initial password to open various versions of the same workbook. But I want to keep the workbook identical for all users but restrict access to certain sheets. Surely there's a password protect function that requires the user to enter a password to view a sheet. Rather than create multiple versions of the same workbook based on different users.
I have tried the following but it doesnt prompt a password to access the sheet
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Dim MySheets As String, Response As String
Dim MySheet As Worksheet
MySheet = "COMMUNICATION"
If ActiveSheet.Name = MySheet Then
ActiveSheet.Visible = False
Response = InputBox("Enter password to view sheet")
If Response = "MyPass" Then
Sheets(MySheet).Visible = True
Application.EnableEvents = False
Sheets(MySheet).Select
Application.EnableEvents = True
End If
End If
Sheets(MySheet).Visible = True
End Sub
Am I doing this right?
It sounds like according to the comments that this isn't as much as a security issue as it is a convenience issue. So please bear in mind when considering implementing this into your project that this is easily breakable if there is any malicious intent to gain unauthorized access.
First, I would recommend a common landing zone. A main worksheet that is displayed immediately after opening a workbook. To do this, we would use the Workbook_Open() event and activate a sheet from there.
This can be a hidden sheet if desired, that will be up to you.
Option Explicit
Private lastUsedSheet As Worksheet
Private Sub Workbook_Open()
Set lastUsedSheet = Me.Worksheets("MainSheet")
Application.EnableEvents = False
lastUsedSheet.Activate
Application.EnableEvents = True
End Sub
Next, we should decide on what should occur when there's an attempt to access a new sheet. In the below method, once a sheet is activated it will automatically redirect the user back to the last used sheet until a successful password attempt has been made.
We can track the last used sheet in a module-scoped variable, which in this example will be named lastUsedSheet. Any time a worksheet is successfully changed, this variable will be set to that worksheet automatically - this way when when someone attempts to access another sheet, it will redirect them back to the prior sheet until the password is successfully entered.
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
On Error GoTo SafeExit
Application.EnableEvents = False
' Error protection in case lastUsedSheet is nothing
If lastUsedSheet Is Nothing Then
Set lastUsedSheet = Me.Worksheets("MainSheet")
End If
' Allow common sheets to be activated without PW
If Sh.Name = "MainSheet" Then
Set lastUsedSheet = Sh
Sh.Activate
GoTo SafeExit
Else
' Temporarily send the user back to last sheet until
' Password has been successfully entered
lastUsedSheet.Activate
End If
' Set each sheet's password
Dim sInputPW As String, sSheetPW As String
Select Case Sh.Name
Case "Sheet1"
sSheetPW = "123456"
Case "Sheet2"
sSheetPW = "987654"
End Select
' Create a loop that will keep prompting password
' until successful pw or empty string entered
Do
sInputPW = InputBox("Please enter password for the " & _
"worksheet: " & Sh.Name & ".")
If sInputPW = "" Then GoTo SafeExit
Loop While sInputPW <> sSheetPW
Set lastUsedSheet = Sh
Sh.Activate
SafeExit:
Application.EnableEvents = True
If Err.Number <> 0 Then
Debug.Print Time; Err.Description
MsgBox Err.Description, Title:="Error # " & Err.Number
End If
End Sub
Side note, disabling events is necessary due to the fact that your Workbook_SheetActivate event will continue to fire after a successful sheet change.
Preventing file type changes during SaveAs1
You can further protect the accidental removal of VBA code by restricting the file save type. This can be accomplished using the Workbook_BeforeSave() event. The reason this is a potential problem is that saving as a non-macro enabled workbook will erase the code, which will prevent the password protection features you just implemented above.
First, we need to check if this is a Save or SaveAs. You can accomplish this using the Boolean property SaveAsUI that is included with the event itself. If this value is True, then it's a SaveAs event - which means we need to perform additional checks to ensure that the file type isn't accidentally changed from the save dialog box. If the value is False, then this is a normal save, and we can bypass these checks because we know the workbook will be saved as type .xlsm.
After this initial check, we will display the dialog box using Application.FileDialog().Show.
Afterwards, we will check if the user canceled the operation .SelectedItems.Count = 0 or clicked Save. IF user clicked cancel, then we simply set Cancel = True and the workbook will not save.
We proceed to check the type of extension selected by the user using this line:
If Split(fileName, ".")(UBound(Split(fileName, "."))) <> "xlsm" Then
This will split the file path by a period ., and will grab the last instance of the period (UBound(Split(fileName, "."))) in the event a file name may contain additional periods. If the extension does not match xlsm, then we abort the save operation.
Finally, after all checks passed, you can save the document:
Me.SaveAs .SelectedItems(1), 52
Since we already saved it with the above line, we can go ahead and set Cancel = True and exit the routine.
The full code (to be placed in the Worksheet obj module):
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
On Error GoTo SafeExit
If SaveAsUI Then
With Application.FileDialog(msoFileDialogSaveAs)
.Show
If .SelectedItems.Count = 0 Then
Cancel = True
Else
Dim fileName$
fileName = .SelectedItems(1)
If Split(fileName, ".")(UBound(Split(fileName, "."))) <> "xlsm" Then
MsgBox "You must save this as an .xlsm document. Document has " & _
"NOT been saved", vbCritical
Cancel = True
Else
Application.EnableEvents = False
Application.DisplayAlerts = False
Me.SaveAs .SelectedItems(1), 52
Cancel = True
End If
End If
End With
Else
Exit Sub
End If
SafeExit:
Application.EnableEvents = True
Application.DisplayAlerts = True
If Err.Number <> 0 Then
Debug.Print Time; Err.Description
MsgBox Err.Description, Title:="Error # " & Err.Number
End If
End Sub
1 Shoutout to PatricK for the suggestion
If you want to restrict access to a worksheet, you can just hide it:
ActiveWorkbook.Sheets("YourWorkSheet").Visible = xlSheetVeryHidden
I concur with Mathieu Guindon that any VBA attempt to "Restrict viewing access to an Excel worksheet" will be flimsy as explained by Mathieu Guindon. Moreover, If the file is opened with Excel option Macro security level other than the lowest, any VBA code including this is bound to fail.
However just for shake of simplicity I prefer to use workbook open event and Sheet Activate of the restricted sheet. Using Workbook Sheet Activate event will trigger password prompt even during switching between sheets by user with viewing access.
Private Sub Workbook_Open()
Sheets("COMMUNICATION").Visible = xlSheetHidden
End Sub
Public ViewAccess As Boolean 'In restricted sheet's activate event
Private Sub Worksheet_Activate()
If ViewAccess = False Then
Me.Visible = xlSheetHidden
response = Application.InputBox("Password", xTitleId, "", Type:=2)
If response = "123" Then
Me.Visible = xlSheetVisible
ViewAccess = True
End If
End If
End Sub

Have Excel VBA wait for PowerQuery data refresh to continue

My scenario : I have a few data tables pulled via PowerQuery that I wanted to have automatically refresh the data, save, and close. I had a task scheduler run these every day at 1 AM. The problem was that Excel VBA doesn't wait for the PowerQuery to update before it goes to the next step (save).
There are a LOT of blogs about this, I didn't find any answer - but it led me to something that worked for me! I'm not proud of the code but here it is:
Public Sub DataRefresh()
DisplayAlerts = False
For Each objConnection In ThisWorkbook.Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.Refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
Workbooks("DA List.xlsm").Model.Refresh
DoEvents
For i = 1 To 100000
Worksheets("DA List").Range("G1") = i
Next i
DoEvents
ActiveWorkbook.Save
Application.Quit
End Sub
I think this works because I gave excel something to do other than the data refresh, and the extra lines between DoEvents and my next step seemed to make VBA finally figure out what I was intending.
Hope this helps!!
Public Sub DataRefresh()
DisplayAlerts = False
For Each objConnection In ThisWorkbook.Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.Refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
Workbooks("DA List.xlsm").Model.Refresh
DoEvents
For i = 1 To 100000
Worksheets("DA List").Range("G1") = i
Next i
DoEvents
ActiveWorkbook.Save
Application.Quit
End Sub

Resources