AppleScript progress bar bug: How to prevent progress dialog from lingering?
The bug:
When AppleScript's progress dialog is succeeded by a dialog, the progress dialog still lingers—even though all progress steps have completed, and the progress bar is full. It will linger until the script has been cancelled, until the script has completed, or until no additional dialogs exist in the script.
This bug cannot be witnessed while running the code from within Script Editor.app, because, in Script Editor, a progress dialog will not appear. Instead, a pie-shaped progress meter is integrated into the bottom of the script window.
How to reproduce the bug:
Save the following AppleScript code as an .app file:
(The file must be an .app file, as .scpt files cannot display progress dialogs.)
set n to 5
set progress total steps to n
set progress description to "Script Progress"
set progress additional description to "Additional description"
repeat with i from 1 to n
delay 0.1
set progress completed steps to i
end repeat
display dialog "The progress dialog should be gone at this point."
Upon launching the application, you will see the following:
The question:
Does a workaround exist to force the progress dialog to close once its complete, so that additional dialogs can be placed after the progress dialog, without having the progress dialog still visible?
What I've tried:
I tried approaching the problem by interpreting the progress dialog simply as a "window" of the app.
If you run the following code in a separate AppleScript file, while both dialogs of ProgressBarTest.app are on screen (as in the above screenshot):
tell application "System Events"
set allWindows to name of window of processes whose visible is true
end tell
return allWindows
you will learn that ProgressBarTest.app has 2 open "windows". The titles of these windows are:
{"", "ProgressBarTest.app"}
The first window in this list refers to the display dialog
dialog. The second window in this list, entitled ProgressBarTest.app
, is the progress dialog.
I then attempted to close this progress dialog "window" by using AppleScript (like one may do for any standard application window). But, the following code:
tell application "System Events" to tell process "ProgressBarTest.app"
if exists window "ProgressBarTest.app" then
close window "ProgressBarTest.app"
end if
end tell
will give the user an error. The text of this error dialog is:
Script Error
System Events got an error: window "ProgressBarTest.app" of process "ProgressBarTest.app" doesn’t understand the “close” message.
I shortly realized that, if your screen is the same point of view as that of the above screenshot, then you cannot even manually close out of the progress dialog. This is because the display dialog
dialog takes precedence over the progress dialog; the display dialog
dialog "grays out" (i.e., disables) all of the buttons in the progress dialog.
So, to account for this, in the ProgressBarTest.app code, I added a delay 5
directly above the display dialog "The progress dialog should be gone at this point."
line. I wanted to see if I could successfully close the progress dialog, if the progress dialog was the only active dialog of the application.
I tried the following code:
tell application "System Events" to tell process "ProgressBarTest.app"
if exists window "ProgressBarTest.app" then
click button 1 of window "ProgressBarTest.app"
end if
end tell
In the above code, button 1
refers to the Stop button that is found in the progress dialog. (You can alternatively use button -4
or button 0
to refer to this same button.)
The good news is that this code successfully closed out of the progress dialog!
The bad news, however, is that when the progress dialog's Stop button is pressed, instead of only the progress dialog being closed, the entire script is cancelled. This is obviously undesirable.
The root of the issue is that the progress dialog contains no red "x"; the left-most circular button in the top bar of this dialog is always grayed-out. In other words, there is no way to close the progress dialog manually, without also prematurely ending the script.
So, this problem is more difficult to solve than I thought.
It appears that my desired outcome is impossible to achieve.
OS X El Capitan, version 10.11.6.
Solution 1:
Works for me on latest version of macOS Big Sur
UPDATE
BEST SOLUTION
If you place the display dialog
command within a System Events tell block
then wrap the ignoring application responses
command around that display dialog
command, the progress indicator
will complete it’s task without waiting for the display dialog
window to be dismissed.
The ignoring application responses
command will only work if it is placed within an application’s tell block
set n to 50
set progress total steps to n
set progress description to "Script Progress"
set progress additional description to "Additional description"
repeat with i from 1 to n
delay 0.1
set progress completed steps to i
end repeat
tell application "System Events"
activate
ignoring application responses
display dialog "The progress dialog should be gone at this point."
end ignoring
end tell
OLD SOLUTION 1
set n to 5
set progress total steps to n
set progress description to "Script Progress"
set progress additional description to "Additional description"
repeat with i from 1 to n
delay 0.1
set progress completed steps to i
end repeat
return
quit
on quit
activate
display dialog "The progress dialog should be gone at this point."
continue quit -- allows the script to quit
end quit
Here is a way to run some repeat loops before the script quits
set n to 5
set progress total steps to n
set progress description to "Script Progress"
set progress additional description to "Additional description"
repeat with i from 1 to n
delay 0.1
set progress completed steps to i
end repeat
return
quit
on quit
activate
display dialog "The progress dialog should be gone at this point."
repeat 5 times
my get_my_IP()
my screenCaptureToDesktop()
delay 1
end repeat
continue quit -- allows the script to quit
end quit
on get_my_IP()
activate
tell current application to display dialog (do shell script "curl ifconfig.co") with icon 2 buttons "OK" default button 1 with title "Your Current IP Address Is.." giving up after 5 -- "curl ifconfig.io" -- alternate
end get_my_IP
on screenCaptureToDesktop()
do shell script "/usr/sbin/screencapture \"" & ¬
POSIX path of (path to desktop as string) & ¬
"Screen Shot " & (current date) & ".png\""
end screenCaptureToDesktop
OLD SOLUTION 2
Working on one of my own projects, I had one of those "EUREKA!" moments. Here is a completely different approach than my code in SOLUTION 1. The approach here was to wrap most of my code in script objects or handlers, then call those objects as needed. In this solution, I saved this script as a stay open application, with explicit run and idle handlers. Some of the code runs when the application is launched, but most of the commands actually happen within the idle handler.
I think this solution has more promise than SOLUTION 1.
global allFiles, thisFile, theFileCount, ProgressBar, mainFolder, backupFolder, theDate
on run
run ProgressBar
end run
on idle
delay 0.2
activate
set theButton to button returned of (display dialog ¬
"The progress dialog should be gone at this point." buttons {"QUIT", "CONTINUE"} ¬
default button ¬
"CONTINUE" with title ¬
"BACKUP UTILITY" with icon 0 ¬
giving up after 10)
if theButton = "QUIT" then
quit
else if theButton = "" then
quit
else if theButton = "CONTINUE" then
try
run ProgressBar
end try
end if
return 1
end idle
on quit
-- Executed when the script quits
continue quit -- allows the script to quit
end quit
script ProgressBar
set mainFolder to choose folder with prompt ¬
"PLEASE CHOOSE YOUR SOURCE FOLDER TO BACKUP"
set backupFolder to choose folder with prompt ¬
"PLEASE CHOOSE YOUR BACKUP DESTINATION FOLDER"
set theDate to (current date) - (14 * days)
tell application "Finder"
set allFiles to entire contents of folder mainFolder as alias list
end tell
set theFileCount to count of allFiles
set progress total steps to theFileCount
set progress completed steps to 0
set progress description to "Processing Files..."
set progress additional description to "Preparing to process."
repeat with theName from 1 to number of items in allFiles
set this_item to item theName of allFiles
tell application "Finder"
duplicate this_item to backupFolder ¬
with replacing
end tell
set progress additional description to ¬
"Duplicating File " & theName & " of " & theFileCount & ¬
" to folder " & backupFolder
set progress completed steps to theName
end repeat
-- Reset the progress information
set progress total steps to 0
set progress completed steps to 0
set progress description to ""
set progress additional description to ""
end script