ASOC & NSSlider

I’m writing a script using Shane Stanley’s Myriad Tables Lib, and I’d like to add a slider to the script. Having never created a slider, and being pretty new to ASOC in general, I tried to buy Shane’s Everyday AppleScriptObjC, but the link to buy the book isn’t working. So I scoured the web on for an example on how to implement a slider using ASOC, and found this:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

property response : missing value -- result from the alert

on run -- UI items need to be run on the main thread
    if current application's NSThread's isMainThread() as boolean then
        doStuff()
    else
        my performSelectorOnMainThread:"doStuff" withObject:(missing value) waitUntilDone:true
    end if
end run

to doStuff()
    try
        showAlert given info:"This is an example using a slider.", buttons:{"OK", "Whatever"}
        -- do stuff with the response
        log response
    on error errmess
        display alert "Error with doing stuff" message errmess
    end try
end doStuff

to showAlert given message:message : "Alert", info:info : "", buttons:buttons : {"OK"} -- reverse order
    set accessory to makeSlider of {275, 26} given initial:0, minimum:0, maximum:10
    tell current application's NSAlert's alloc's init()
        set its |window|'s autorecalculatesKeyViewLoop to true -- hook added views into the key-view loop
        
        # cheats to 'hide' the alert's bold message text field, and increase the size
        # of the informative text field (comment or remove for normal operation):
        (its |window|'s contentView's subviews's item 5)'s setFont:(current application's NSFont's boldSystemFontOfSize:0.25) -- something small
        (its |window|'s contentView's subviews's item 6)'s setFont:(current application's NSFont's systemFontOfSize:13) -- something bigger
        
        its setMessageText:message
        its setInformativeText:info
        repeat with aButton in buttons
            (its addButtonWithTitle:aButton)
        end repeat
        set its accessoryView to accessory
        set theButton to item ((its runModal() as integer) - 999) of buttons -- rightmost button returns 1000
        set response to {button:theButton, slider:(accessory's intValue) as integer}
    end tell
end showAlert

to makeSlider of |size| given origin:origin : {0, 0}, initial:initial : 0.0, minimum:minimum, maximum:maximum, action:action : missing value
    tell (current application's NSSlider's sliderWithValue:initial minValue:minimum maxValue:maximum target:me action:action)
        its setFrame:{origin, |size|}
        its setNumberOfTickMarks:(maximum + 1)
        set its allowsTickMarkValuesOnly to true
        # set other properties as desired
        return it
    end tell
end makeSlider

from here.
The script works well, but is a bit of a work-around. I have 2 questions:

  1. Is there a less kludgy way of implementing a slider?
  2. How do I then link the NSSlider instance to an NSTextField instance to display the value of the slider? An example would be greatly appreciated - again, I’m pretty new to this and I’m not familiar with most of the terminology and processes. Thanks so much.

The most “Non-kludgy” way I’ve found when building any UI stuff is using Xcode and it’s interface builder. You create things visually and connect them thru outlets.

This requires learning a bit of XCode and IB.
But when I was expanding my programming and wanting to integrate interface elements. I learned how to use Xcode with AppleScript.
It was slow but so worth it.

The are some Xcode examples about using XCode with a base AppleScript.

Also there’s and Xcode project example showing how to bind a NSSlider value to a NSTexrfield

1 Like

And older tutorial on IB elements

technomorph, thanks, but I’m specifically building this project with the Myriad Tables Lib script library, so using Xcode won’t work. But if you can point me to the Xcode example of binding a NSSlider to a NSTextfield, maybe I can extrapolate from that.

Here is a sample script for NSSlider. It works.

http://piyocast.com/as/archives/10103

1 Like

Piyomaru, thanks so much for that! I hate to ask, but now how do I tie this to a NSTextField so that there is a way for the user to see exactly what value the slider represents?

To do that, you’ll need to use the Cocoa Binding.

It’s rather cumbersome to do that with AppleScript… It seems that it is less troublesome to create a GUI application with AppleScript on Xcode.

You will need to declare an action handler that has the statements to update the text field, and set the NSSlider (NSControl) target/action properties for it. NSTextField has a few convenience methods to set up simple instances, although it can also get kludgy depending on what you are doing.

Objective-C, AppleScriptObjC, you name it - pretty much anything will get wordy when creating the UI programmatically, there is just a lot of stuff in Cocoa that gets hidden by the Interface Builder. I wrote that script as a minimal example - some classes have a bunch more options and can be complex to set up, involving other classes and delegate methods, so the overall kludginess depends a bit on the specific details.

Following is an update of that example, using an action handler to put the slider value into a text field - haven’t used Myriad Tables enough to integrate it, though:

use AppleScript version "2.5" -- Sierra (10.12) or later for new enumerations
use framework "Foundation"
use scripting additions

property textField : missing value -- UI outlet - can also get the textField from the view hierarchy
property response : missing value -- alert result - performSelectorOnMainThread doesn't return anything

on run -- UI items need to be run on the main thread - if an app, the handler can be called directly
   my performSelectorOnMainThread:"doStuff" withObject:(missing value) waitUntilDone:true
   return response -- whatever
end run

to doStuff()
   try
      showAlert for "" given info:"An example of using a slider with a text field:", buttons:{"OK", "Cancel"}
   on error errmess
      display alert "Error with doing stuff" message errmess
   end try
end doStuff

# Build, show, and get a result from a NSAlert - the response property will be set accordingly.
to showAlert for message as text : "Alert" given info:info as text : "", buttons:buttons as list : {"OK"}
   tell current application's NSAlert's alloc()'s init()
      its (|window|'s setAutorecalculatesKeyViewLoop:true) -- hook added views into the key-view loop
      tell its |window|'s contentView's subviews -- alter built-in message and informative text field settings
         #(its fifth item)'s setFont:(current application's NSFont's boldSystemFontOfSize:0.1) -- something smaller
         (its sixth item)'s setFont:(current application's NSFont's systemFontOfSize:13) -- something bigger
      end tell
      its setMessageText:message
      its setInformativeText:info
      repeat with aButton in buttons -- buttons list is from top/right - cancel can be at the bottom in current systems
         (its addButtonWithTitle:aButton)
      end repeat
      its setAccessoryView:(my makeAccessory())
      set theButton to item ((its runModal() as integer) - 999) of buttons -- rightmost button returns 1000
      set slider to item (((theButton is "Cancel") as integer) + 1) of {(textField's stringValue) as real, missing value}
      # if slider is missing value then error number -128 -- uncomment to throw an error on cancel
      set response to {button:theButton, controlValue:slider}
   end tell
end showAlert

# Make and return an accessory view for the alert - view subviews are in the order added.
to makeAccessory()
   tell (current application's NSView's alloc()'s initWithFrame:{{0, 0}, {300, 60}}) -- can also use NSBox
      its addSubview:(my (makeSlider at {10, 0} given maximum:10))
      its addSubview:(my (makeTextField at {10, 40} with label given stringValue:"Slider value:"))
      set my textField to (my (makeTextField at {90, 35} without autoSize given dimensions:{50, 24}, stringValue:"0.000"))
      its addSubview:textField
      return it
   end tell
end makeAccessory

to makeSlider at origin as list given dimensions:dimensions as list : {275, 26}, initial:initial as real : 0.0, minimum:minimum as real : 0.0, maximum:maximum as real : 10.0, action:action as text : "sliderAction:"
   tell (current application's NSSlider's sliderWithValue:initial minValue:minimum maxValue:maximum target:me action:action)
      its setFrame:{origin, dimensions}
      its setNumberOfTickMarks:(maximum * 2 + 1) -- double to include ticks for half values
      # its setAllowsTickMarkValuesOnly:true -- uncomment to limit values to the tickmarks
      # its sendActionOn:(current application's NSEventMaskLeftMouseUp) -- uncomment to only send values on mouseup
      # other properties as desired
      return it
   end tell
end makeSlider

# Perform an action when the slider is used.
# Individual subviews can also be extracted from the sender's superview.
on sliderAction:sender
   tell current application's NSNumberFormatter's alloc()'s init()
      its setMinimumFractionDigits:3
      textField's setStringValue:(its stringFromNumber:(sender's doubleValue)) -- formatted slider value
   end tell
end sliderAction:

# Make and return a textField or label.
to makeTextField at origin as list given dimensions:dimensions as list : {100, 24}, stringValue:stringValue as text : "", label:label as boolean : false, autoSize:autoSize as boolean : true
   tell current application's NSTextField to ¬
      tell item ((label as integer) + 1) of {its textFieldWithString:stringValue, its labelWithString:stringValue}
         its setFrame:{origin, dimensions}
         if autoSize then its sizeToFit()
         # other properties as desired
         return it
      end tell
end makeTextField
2 Likes

Is it possible to change the icon, or move its’ location?

You can change the icon, but that is about it. An alert is handy (especially for examples) because it already has options for stuff like an accessory view and buttons - a window or panel can also be used, there is just more to set up.

That’s fantastic! Thanks again!