MacOS – AppleScript progress bar bug: How to prevent progress dialog from lingering

applescriptbugdialogmacos

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.


Best Answer

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

enter image description here



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