问题
Is there any way to make a progress bar that is loading as a shell script or shell command runs?
I currently use the following:
property theWindow : missing value
property parent : class "NSObject"
property customView : missing value
property myProgressBar : missing value
on hardwareData()
set alert to current application's NSAlert's alloc's init()
tell alert
its setAccessoryView:customView
its setMessageText:"Loading Informations"
its setInformativeText: "Please Wait..."
its setAlertStyle:1
its addButtonWithTitle:"Cancel"
its setShowsSuppressionButton:false
its beginSheetModalForWindow:theWindow modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
end tell
delay 0.02
set hardwareData to do shell script "system_profiler SPHardwareDataType"
(*This loop loads the progress bar but only after the
shell command was executed and not at run time.*)
set c to 0
repeat 100 times
set c to c + 1
delay 0.06
tell myProgressBar to setDoubleValue_(c)
if c > 99 then
exit repeat
end if
end repeat
end hardwareData
I think this is a fake progress bar once which does not execute together with the shell script.
回答1:
The user interface will be blocked if you don't give the system time to handle events - by using a tight repeat loop, for example. If a loop is needed, you can periodically call a handler to update the UI, or manually handle system events. For a third party scriptable progress indicator background application there is also the SKProgressBar from one of the MacScripter regulars.
If you are planning to use a shell script, note that it will also block the user interface if it takes time to complete, and may not provide feedback that you can use for its progress. Anything that will take time to to complete should be performed with an asynchronous background task, but AppleScriptObjC is a bit limited in that regard. NSTask provides a way to perform background tasks with notifications, so you might want to check that out, as its use and arranging your app around the notifications is another subject.
You should start using an Objective-C category that provides access to the new block-based alert methods, but to keep using the old deprecated sheet method you will need to use action handlers for any buttons (such as a cancel) you want to add. The following Xcode project (just create a blank AppleScriptObjC project and copy to the AppDelegate file) uses your counter to simulate the progress:
# AppDelegate.applescript
script AppDelegate
property parent : class "NSObject"
property theWindow : missing value
property alert : missing value -- this will be the alert
property myProgressBar : missing value -- this will be the progress indicator
property alertCancel : false -- this will be a flag to indicate cancel
to makeButton(title, x, y) -- make a button at the {x, y} position
tell (current application's NSButton's buttonWithTitle:title target:me action:"buttonAction:")
its setFrame:{{x, y}, {120, 24}}
its setRefusesFirstResponder:true -- no highlight
return it
end tell
end makeButton
on buttonAction:sender -- perform the alert
if alert is missing value then tell current application's NSAlert's alloc's init()
set my alert to it
its setMessageText:"Loading Informations"
its setInformativeText:"Please Wait..."
set cancelButton to its addButtonWithTitle:"Cancel"
cancelButton's setTarget:me
cancelButton's setAction:"cancelButton:"
its setAccessoryView:(my makeIndicator())
end tell
set my alertCancel to false -- reset
myProgressBar's setDoubleValue:0
alert's beginSheetModalForWindow:theWindow modalDelegate:me didEndSelector:(missing value) contextInfo:(missing value)
doStuff()
end buttonAction:
on cancelButton:sender -- mark alert as cancelled
set my alertCancel to true
current application's NSApp's endSheet:(alert's |window|)
end cancelButton:
to makeIndicator() -- make and return a progress indicator
alert's layout()
set theSize to second item of ((alert's |window|'s frame) as list)
set width to (first item of theSize) - 125 -- match alert width
tell (current application's NSProgressIndicator's alloc's initWithFrame:{{0, 0}, {width, 22}})
set myProgressBar to it
set its indeterminate to false
set its maxValue to 100
return it
end tell
end makeIndicator
on doStuff() -- the main progress loop
set c to 0
repeat 100 times
set c to c + 1
delay 0.06 -- do something
tell myProgressBar to setDoubleValue:c
fetchEvents()
if c > 99 or alertCancel then exit repeat
end repeat
current application's NSApp's endSheet:(alert's |window|)
end doStuff
on fetchEvents() -- handle user events
repeat -- forever
tell current application's NSApp to set theEvent to its nextEventMatchingMask:(current application's NSEventMaskAny) untilDate:(missing value) inMode:(current application's NSDefaultRunLoopMode) dequeue:true
if theEvent is missing value then return -- none left
tell current application's NSApp to sendEvent:theEvent -- pass it on
end repeat
end fetchEvents
##################################################
# Delegate methods
##################################################
on applicationWillFinishLaunching:aNotification
theWindow's contentView's addSubview:makeButton("Show Alert", 180, 30)
end applicationWillFinishLaunching:
on applicationShouldTerminate:sender
return current application's NSTerminateNow
end applicationShouldTerminate:
end script
来源:https://stackoverflow.com/questions/59096595/progress-bar-with-applescript