How to disable ribbon while VBA code is running - vba

I need to cancel button clicks on a Custom Ribbon if code is already running.
The problem: although I can disable the button, clicks are still 'registered' and even though disabled, the corresponding code runs once for each button click.
Public Sub OnActionButton(control As IRibbonControl)
' The following approach fails to prevent code from running
If Not globalEnabled Then Exit Sub
' The following approach fails to prevent code from running
Dim goodToGo as Boolean
GetEnabled control, goodToGo
If Not goodToGo Then Exit Sub
' *********************************************************
' Code Below Here Should NOT Run If Button Was Clicked
' While globalEnabled = False (i.e. code already running)
' *********************************************************
' Disable the Ribbon
globalEnabled = False
RibbonInvalidate
Select Case control.id
Case "btnID"
' doSomething
End Select
' Re-enable the ribbon
globalEnabled = True
RibbonInvalidate
End Sub
Edit: For what its worth, this is how I disable the button:
Public Sub GetEnabled(control As IRibbonControl, ByRef enabled)
' Called by RibbonInvalidate
Select Case control.id
Case "btnID"
enabled = globalEnabled
End Select
End Sub
I can imagine that the first click runs as expected, the second click (which is made while the code corresponding to the first click is running) is 'queued' until the first click is completed. At that time globalEnabled is True again so the checks are ineffective.
I guess I could hide the custom ribbon tab (or replace it with a dummy tab) - but is that really necessary?
Is there an elegant way to cancel a button press?
Edit:
I could add a bunch of doEvents to the code, but slows things down too much.
Edit2:
If I display a message box during code execution then, for some reason, it seems that things work as expected...
In fact... if I display a message box then I don't even need the early exit logic - so weird!

This works, main thing was adding doEvents (or a message box) at the end.
Public Sub OnActionButton(control As IRibbonControl)
' Disable the Ribbon
globalEnabled = False
RibbonInvalidate
Select Case control.id
Case "btnID"
' doSomething
End Select
' Re-enable the ribbon
doEvents ' <-- Adding this fixed the issue for me
' msgbox "finished" ' <-- also works, but pretty annoying ;-)
globalEnabled = True
RibbonInvalidate
End Sub

Im late to the party, but are you declaring GlobalEnabled as a global variable? Because from my understanding and testing (on a similar problem) if it is not explicitly declared as global, each time the ribbon button is clicked, a local variable of GlobalEnabled is created rather than using the value set in the prior run of the macro.

Related

Click event on one control sometimes does not fire

I have two buttons on a sheet. On occasion, clicking on one of the buttons does not "fire" the event.
Floating the cursor over the button changes the cursor from a cross to an arrow
The button does not appear to "depress" as it would if the event fired.
Breakpoint or Stop statement in the code do not get reached.
The other button never demonstrates this problem.
If the code is started manually, the code will run as designed.
In Designer Mode, the code appears to be attached to the button.
Closing and re-opening the worksheet, and then triggering the button that normally works will restore functionality to this button.
Trouble shooting advice would be appreciated.
The button code is below as is the properties window, but I think the problem lies elsewhere; just don't know where to look.
Again, another very similar button, which calls different code, seems to work OK all the time.
Thanks for any guidance.
Option Explicit
Private Sub cbDeleteViewed_Click()
With cbDeleteViewed
.Width = .Width
End With
Application.ScreenUpdating = False
Select Case Environ("COMPUTERNAME")
Case "RON-DODIER"
sDrive = "F:\"
sBasePath = "Videos"
ProtectDisable Sheet1
UnProtectEnable Sheet3
Sheet3.Select
Case "RONBP"
sDrive = "Z:\"
sBasePath = ""
ProtectDisable Sheet3
UnProtectEnable Sheet1
Sheet1.Select
Case Else
MsgBox "Cannot Run on This Computer"
Exit Sub
End Select
SetUpZ
DeleteViewedShows
Application.ScreenUpdating = True
End Sub

Call a sub after this one has finished

I have a userform looping through a range with 2 settings; manual and automatic.
When I have an option button on my form set to manual, and click a next command button, I check the next cell in the range, change the contents of the form accordingly, then wait for the next button press.
However if I find the option button is set to automatic then instead of finishing up my code and waiting for the next button press, I have to call the next button press programmatically. That means that the previous subs each calling the next code slowly build up in the call stack, and I worry this will have some memory implications if I'm looping over a large range.
In code:
Private Sub nextItem()
Dim willShow As Boolean
returnResults 'return details from the form to the sheet
clearMemory 'clear out previous items on form
itemNo = itemNo + 1 'iterate over the range
SetupParts 'place new items on form
'do what next
Select Case displaySetting 'this variable holds the result from my option button in a custom enum "dispMode"
Case dispMode.Manual 'always show form
willShow = True
Case dispMode.SemiAutomatic 'show form based on condition
willShow = (Data.Count = 0) 'if SetupParts returns no data, display form, otherwise keep hidden
Case dispMode.Automatic 'don't show form
willShow = False
End Select 'there are actually a few more options here, but I've simplified
If willShow = False Then
If Me.Visible = True Then 'if needs to hide, and currently visible, then hide the form
Me.Hide
nextItem 'this is the problem, I call this code again, so the call stack grows
Else
'form is already hidden, do nothing
End If
ElseIf Me.Visible = False Then 'willShow must be True
Me.Show 'then wait for button click
End If
End Sub
Private Sub commandBtnNext_Click()
nextItem
End Sub
To avoid this problem, is there any way of getting nextItem to run immediately after the previous call of nextItem has run; ie. to tell a sub to run immediately after this one has finished (without introducing time delays). Or maybe this isn't an issue; if so, please explain why.
UPDATE
There is also a SemiAutomatic check to see which mode to use based on the contents of the userform. This is fine when calling recursively, but I can't see how to incorporate it into a looping approach.

Excel VBA Ribbon getEnabled not called when code running

I have an Excel VBA macro that runs for a long time (potentially for days, it performs data acquisition). It was originally written for Excel 2003 and has custom toolbars and menus.
I recently updated it to use a ribbon interface using RibbonXML.
When the macro is running I want to disable some interface elements (such as "start test"), and enable others (such as the "stop test" button.)
The problem I have is that calls to ribbon.invalidate are only processed after the macro code has run to completion.
You can see this effect quite easily with a simple test program
Sub test()
ribbon.Invalidate
DoEvents
Sleep (5000)
End Sub
A debug.print in the ribbon "getEnabled" callback will be seen to only be actioned at the end of the 5 second sleep.
Is there any way to force a ribbon.Invalidate to be activated there and then?
:: Edit 1 ::
I have created a small demo workbook to ilustrate the issue:
http://www.bodgesoc.org/Button_Demo.xlsm
:: Edit 2 ::
A member of a different forum found a solution, though it is a slightly ugly one.
I guess this can now be marked as "answered" but a more elegant solution would be appreciated.
Application.ScreenUpdating = False
Application.ExecuteExcel4Macro "Show.ToolBar(""Ribbon"",False)"
Application.ExecuteExcel4Macro "Show.ToolBar(""Ribbon"",True)"
Application.ScreenUpdating = True
I don't know if this is any less ugly than what you have, but...
'Callback for Run onAction
Sub Run_r(control As IRibbonControl)
SetRunning "Run_r_procedure"
DoEvents
End Sub
Sub Run_r_procedure()
Dim T As Single
Sheet1.Range("A10").Value = "Run pressed, button states changed, and ribbon invalidated. Waiting 5 seconds in loop"
T = Timer
Do While Timer - T < 5
DoEvents
Loop
Sheet1.Range("A10") = ""
End Sub
And then in SetRunning
Sub SetRunning(ByVal ProcToRun As String)
Sheet1.Range("B1") = "Disabled"
Sheet1.Range("B2") = "Disabled"
Sheet1.Range("B3") = "Enabled"
Sheet1.Range("B4") = "Enabled"
Sheet1.Range("B5") = "Enabled"
Sheet1.Range("B6") = "Enabled"
Sheet1.Range("B7") = "Enabled"
myRibbon.Invalidate
myRibbon.InvalidateControl ("Run")
Application.OnTime Now, ProcToRun
End Sub
So you have to have two procedures per callback - the callback and then whatever SetRunning will call that does the actual work. The code is just as ugly, but the UI is little less strange looking to the user.

Pause VBA macro, allow user to make a selection, and restart where it left off

I want to allow the user to make a selection, run some code, pause for another selection, run more code?
I work with documents with large number of tables that eventually convert to HTML. Sometimes the formatting on two like tables doesn't convert the same. Knowing how the converter works, I'd like to copy all of the formatting data from one table and "paste" it onto another one.
I've the idea of a userform to have the user select something, hit a copy button, select something else and hit a paste button.
The timer function allows you to do this. It may not be the best way to code, but it is the answer to your problem:
'1st code here
Start = Timer
Do While Timer < Start + 10
DoEvents
Loop
'do 2nd code here
DoEvents allows the user to select text, etc. After 10 seconds, the code resumes at "2nd code."
You can use global a global variable:
Public myVar as Variant
Sub fillmyVar()
myVar = Selection
End Sub
Sub doSth()
' use myVar and the new selected text
End Sub
Using the answer from Aaron and incorporating it with a ToggleButton in the Userform you can successfully pause the code. With this you can then work in an additional selection to change the operation.
I originally did not use Global or Public Variables but soon learnt that its easier for passing data between Subs and Userforms
Userform:
Public Sub ToggleButton1_AfterUpdate()
'Program is Paused / Selected to Pause
If ProgBar.ToggleButton1.Value = True Then
'Changing the Text of the Toggle button once the program is selected to Pause
'If program paused then button will display continue
ProgBar.ToggleButton1.Caption = "Continue"
'For Sending to Loop Code
ProgramStatus = "0"
Call LoopCode.PrgStat(ProgramStatus)
End If
'Program is running / Selected to run
If ProgBar.ToggleButton1.Value = False Then
'Changing the Text of the Toggle button once the program is selected to continue
'If program running then button will display pause
ProgBar.ToggleButton1.Caption = "Pause"
'For Sending to Loop Code
ProgramStatus = "1"
Call LoopCode.PrgStat(ProgramStatus)
End If
End Sub
In your Module
Public Status As String
Public Sub PrgStat(ByVal ProgStatus As String)
Status = ProgStatus
End Sub
Sub SomeSub()
Do While
' Some Loop Code Running
If Status = "1" Then
'Toggle Not Pressed
End If
If Status = "0" Then
'Toggle Button Pressed
Do While Status = "0"
'Program will stop here until the togglebutton on the
'userform is pressed again which changes Status = 1
'This is where you can make another selection on the
'userform
DoEvents
Loop
End If
Loop
End Sub

Userform disable all fields

I have a userform that can be filled in but only if a previous userform was filled in as there are calculations in the userform based on the previous input and if those are empty, the calculations crash.
Now I did write a few if statements that check for these empty values and then had a brain flash.. how about if one of the fields is missing, the userform is just disabled. So thought, so done and it works :)
if DP1 = "" then
reportback.enable = false
else
end if
That is the form and it shows up beautifully and nothing can be changed, but Ohhh.. you canĀ“t even close the form, nothing works .. lol.
So my question. Is there a way to disable all fields from any input but still have the cancel button active ?
Private Sub Cancel_Click()
Unload reportback
End Sub
Use below code to disable all controls on your form to avoid the issue.
UserForm1 refers to name of Userform kindly replace accordingly.
Dim ctrl As Control
For Each ctrl In UserForm1.Controls
ctrl.Enabled = False
Next
Set ctrl = Nothing