Is it possible to add UI elements such as Text Fields and Popup Buttons to windows programmatically in AppleScript Studio, or is this only possible with Cocoa and Objective-C?
Thanks.
-Rob
Is it possible to add UI elements such as Text Fields and Popup Buttons to windows programmatically in AppleScript Studio, or is this only possible with Cocoa and Objective-C?
Thanks.
-Rob
The easiest way I could think of to do it would be to add everything in IB at once and make it all invisible. Then when the program requires that certain elements be present, just set their visibility to true/false with Applescript.
But if there is a way to build windows dynamically without using C, I also would like to know.
No there is no way to create new UI elements in AppleScript Studio using pure AppleScript. Using some Objective-C it is possible to create them but I am not sure if it is possible to make them 100% useful for AppleScript*. The problem is the connection back to the script’s handlers for user interaction. For example controls use ‘ASKActionEventHandler’ objects for their actions internally - but there is no documented way to programmatically create them or retrieve them from your script as far as I know.
So you could either use The Expreiences Noob’s idea - or you might use button/text field cells in a table view, outline view or matrix to get some kind of dynamic in your interface. If this is not sufficient you better write that part of your app in Objective-C …
*here a quick experiment I made:
[code]// NSApplication_(AppleScriptUIAdditions).h
#import <Cocoa/Cocoa.h>
@interface NSApplication (AppleScriptUIAdditions)
-(NSView *)cloneView:(NSView *)aView asSubViewOf:(NSView *)superView;
@end[/code]
[code]// NSApplication_(AppleScriptUIAdditions).m
#import “NSApplication_(AppleScriptUIAdditions).h”
@implementation NSApplication (AppleScriptUIAdditions)
-(NSView *)cloneView:(NSView *)aView asSubViewOf:(NSView *)superView{
NSView *newView = [ [[aView class] alloc] initWithFrame:[aView frame] ];
if ([aView isKindOfClass:[NSControl class]]) {
[(NSControl *)newView setTarget:[(NSControl *)aView target]];
[(NSControl *)newView setAction:[(NSControl *)aView action]];
}
[superView addSubview:newView];
return [newView autorelease];
}
@end[/code]
adding this NSApplication category it was possible for example ‘cloning’ a button:
on clicked theObject
set newButton to (call method "cloneView:asSubViewOf:" with parameters {theObject, (content view of window of theObject)})
set bds to bounds of newButton
set item 2 of bds to (item 2 of bds) - 40
set item 4 of bds to (item 4 of bds) - 40
set bounds of newButton to bds
set title of newButton to "test"
end clicked
A click on the button now creates a new button 40 px below. It’s possible to set an individual title, style etc. and the clicked handler is also called when clicking on th new button. BUT - theObject of the new button is the old button since I simply have copied it’s target & action in my method. So it’s a nice test but not really useful as said above …
There really is no major problem creating objects programmatically, but it MUST be done in obj-c… there’s no pure ASStudio way of doing it at this time. The best way is to create an obj-c method that creates the object and then returns the object to your script so you can reference it. Often, the trick is to create methods that not only create the object, but also add it to your context, as well. What I mean is, that ASStudio doesn’t always handle object and class references appropriately, so depending on what kind of objects you are creating it’s sometimes best to account for this in your obj-c method when you create them instead of trying to pass the object back to your script and try to deal with it there. For example, in some code I posted for an itunes artwork-related thread, the obj-c method required the creation of a data object representing image data, which then was used to populate an image view. Since ASStudio didn’t natively recognize the data type, it was best to pass a reference to the image view to the obj-c, and set the image in the method… instead of trying to pass the image data back to AS and trying to set the image there. In cases where all you are doing is instantiating something like a button or a window, it’s generally a straightforward process… the trick for ASStudio people being understanding the obj-c side of things.
Actually, this isn’t really very difficult at all. The key is to use a script object, which you can connect to pretty much any object in ASStudio to handle it’s actions. Way back, about 4 years ago there was a post here that dealt with this very issue. I can’t find it now, but I do still have the example project. Essentially, you create an obj-c method that initializes your object, which you call from your script. The obj-c method returns the object reference to the script, which you then use to do any further configuration of the object. The key bit of code is the “set script of someThing…” step. When you connect the object to the script object, it will trigger any actions within the script object that it supports. For example, in the code below I set up a window dynamically in my main script, add a button to it programmatically, and assign a script object to the button. When the button is clicked, it triggers the ‘clicked’ handler contained in the script object, where you need to place all of your relevant code.
script catchButtons
on clicked theObject
display dialog ((name of theObject & ":" & ((state of theObject) as boolean)) as string)
end clicked
end script
on choose menu item theObject
if name of theObject is "dynamicWindow" then
set contentRect to {100, 100, 300, 300}
set {textureBool, closeableBool, miniaturizableBool, resizableBool} to {false, true, true, true}
set DYNobjects to call method "alloc" of class "DYNobjects"
set newWindow to call method "makeWindow:useTexture:isCloseable:isMiniaturizable:isResizable:" of DYNobjects with parameters {contentRect, textureBool, closeableBool, miniaturizableBool, resizableBool}
set name of newWindow to "myWindow"
set contentView to content view of newWindow
set newButton to call method "createButtonIncontentView:frame:state:continuous:" of DYNobjects with parameters {contentView, {20, 42, 120, 142}, 0, true}
set name of newButton to "Test"
set title of newButton to "My Button"
set script of newButton to catchButtons
show newWindow
Note that this stuff was hacked together back when I knew little about obj-c, so it could certainly be cleaned up or redesigned. Use it as a starting point for coming up with a more customized solution. Ultimately, though, the point you should walk away from this with, is that you shouldn’t do this. As The Experienced Noob says, it’s better to design the interface so that you don’t need to go dynamically adding and removing objects on a whim. While there certainly are cases where this is appropriate, it’s usually best to avoid this approach.
ahh - nice. The disadvantage of this is, that it seems to be impossible to access the same script. Trying this:
set script of newButton to me
will create a new instance of the calling script - all globals and properties are gone for the clicked handler …
Meanwhile I partially found out how to create the connection from Objective-C and managed to let my above example create different buttons which are all calling the same handler of my script. But it’s stuff from AppleScriptKit I haven’t found in the docs - all private I guess?? - so I won’t publish it here - in case someone is interested feel free to pm me …
I certainly wouldn’t recommend mucking around in private methods or using weird hacks to handle this sort of thing. Usually apple’s private method names are either preceded by an underscore, or they are documented to be private in the headers. As you’ve probably heard before, using undocumented or private methods can lead to future breakages or incompatibilities, so they’re not usually encouraged. Either way, chances are if you’re calling methods of the applescriptkit on your ASStudio project from obj-c code to interact with your applescript code, you’re going about things the wrong way. In fact, if you’re knowledgeable enough to even consider and do this sort of thing, you shouldn’t be using ASStudio… you should be using obj-c.
The whole point of the script object functionality as it relates to this is that it fills this exact gap we are discussing.
You gotta remember that applescript is not just applescript studio… it’s also still applescript the language. It’s been around for years and has a lot of features that people are increasingly forgetting about. Your main script is itself a script object, and the script object attached to your button is a child of that script… and with this relationship you get ‘inheritance’ for free. One of the nice parts of inheritance is that if you don’t want to handle some command in the script object, you can simply hand it off to the parent with a single word… “continue”.
script catchButtons
on clicked theObject
continue clicked theObject
end clicked
end script
No matter how good you are, or how long you’ve been around, it can never hurt to go back (way back :P) and reread the documentation.
I 100% agree. I just tried to find out this to satisfy my own curiosity and would never recommend using it in a public project. That’s why I haven’t posted it here - but I offered it on a private level … maybe there are other members as curious as I am
And yes - most of my projects are done in C/C++ and Objective-C - but I like AppleScript and especially a mixture of it with Objective-C 'enhancements.
well spoken I tried it but haven’t managed to make it work - can you explain what’s goind wrong?
I have a button connected to the on clicked handler and have added your script object:
property buttoncount : 1
script catchButtons
on clicked theObject
continue clicked theObject
end clicked
end script
on clicked theObject
set newButton to (call method "cloneView:asSubViewOf:" with parameters {theObject, (content view of window of theObject)})
set bds to bounds of newButton
set item 2 of bds to (item 2 of bds) - 40
set item 4 of bds to (item 4 of bds) - 40
set bounds of newButton to bds
set title of newButton to ("button" & buttoncount)
set script of newButton to catchButtons
set buttoncount to buttoncount + 1
end clicked
now when I click the original button a first ‘copy’ is created and is titeled ‘button1’ - fine.
when I click this new ‘button 1’ another button copy is created below - but it’s not titeled ‘button2’ as expected - it’s name is ‘button1’ - why?
btw - same effect as directly setting ‘me’ as script of the new button …