Ok, it’s late and I can’t think clearly, as I try for two hours now to find a solution and get no results.
I have a set of checkboxes in a dialog, which the user can put on or off. Before calling a method, I want to make a list with the check boxes’ titles whose value is on.
I could make so many properties as I have check boxes, and then add them one by one to my list; I could test inside the method by getting each box like this:
If myBoxA as integer is one then (add its title to the list)
If myBoxB as integer is one then (add its title to the list)
If myBoxC as integer is one then (add its title to the list).
The single idea makes me sick. I have 16 checkboxes.
My wish is to have a dictionary where I could have {value: true, name: “Checkbox one”}, make an array of them, and then bind the whole thing in IB (to each control), so
a) the title of the checkboxes will be set programmatically by the array;
b) before calling the next method, I could get a list of titles whose box is on
I’m fighting against Xcode for too long to hope finding suddenly the solution. The concept is clear to me, but as usual I get errors “cannot create BOOL value from.”, or titles like “( )”. To put it politely, I despair.
I don’t think you can do what you want with bindings, but there are easier ways than doing them one by one. You could have the check boxes in a matrix, and just loop through the matrix’s cells() adding the title of any check box whose state is 1 to an array. Or, you could group them in an NSBox, and loop through the box’s subviews() (actually it might be the box’s subviews()'s lastObject()'s subviews() because I think a box has a content view as its one top level view). For that matter, you could just loop through the dialog window’s subviews, checking that the subview is one of your check boxes (by tag or class type depending on what else you have in the dialog).
I know you’re very fond of bindings, but they’re not the answer to everything.
think object oriented !
solution with a few lines of code, assuming there are 4 checkboxes (alphaBox, betaBox, gammaBox, deltaBox)
script SKAppDelegate
property parent : class "NSObject"
property alphaBox : missing value
property betaBox : missing value
property gammaBox : missing value
property deltaBox : missing value
property arrayController : missing value
property array : {}
on awakeFromNib()
set my array to {my alphaBox, my betaBox, my gammaBox, my deltaBox}
arrayController's setFilterPredicate_(current application's NSPredicate's predicateWithFormat_("state == 1"))
end awakeFromNib
on buttonPushed_(sender)
arrayController's rearrangeObjects()
set itemList to arrayController's arrangedObjects's valueForKey_("title")
display dialog (itemList's componentsJoinedByString_(return) as text)
end buttonPushed
end script
connect the boxes to the appropriate outlet properties and bind their actions to the buttonPushed_() method.
Add an NSArrayController in InterfaceBuilder. Connect it to the arrayController property and bind content array to App Delegate.array
If you’re using a table view, you could bind the table view column directly to arrayController.arrangedObjects.title
In applicationWillFinishLaunching_, get the subview list as Ric said (ok, it’s a bit procedural to search under the skirt of an object, but the subviews instance variable is not private.)
Set checkBoxesArray to gCBBox's subviews()'s lastObject()'s subviews() -- store this in a NSMutableArray property.
Now use your checkBox method (more object-oriented indeed), assuming each CB calls it when clicked:
on checkBoxClicked_(sender)
if sender's integerValue is 1 then -- maybe we should coerce a bit here, in ASOC it's a pain.
checkBoxesArray's addObject_(sender)
else
checkBoxesArray's removeObject_(sender)
end if
end
So I have always a updated list of “ON” check boxes – with the correct |count|().
In a pure object-oriented applications (for the moment I only dream of it), I suppose I would add some methods to the NSMutableArray, or better to the NSBox, some of a
Ok to set a predicate to the controller. I suppose rearrangeObjects() does make some kind of loop, that’s were the predicate is triggered. but not in my code
So, when a button is clicked, it calls the method:
on buttonPushed_(sender)
arrayController's rearrangeObjects() -- which runs the "loop" and adds/removes the sender from the arrayController's array according to the predicate's condition ("state == 1")
end buttonPushed
And that’s it? Later, I suppose that if I want to get the tag of one of my controls, I could make
set theTag to (item xxx of arrayController's arrangedObjects)'s tag as integer --?
Ok, I’ll give it a try. I knew you would eventually convince me with predicates.
No, again think object oriented! The predicate sends the message “tell me if you match the predicate otherwise shut up” to each object in the controller (simultaneously!).
If you want the user to select one or multiple items in the list, use a table view, bind arrangedObjects.title to the column and get the selection with bindings (Selection Indexes) or one of the methods selectedObjects() or selectionIndexes()
The NSPredicate works perfectly well. As you said, no loop, no if. Yeah, 10 lines of ugly code may die.
After you have tested something, it’s incredible how the docs become clearer. So this is the arrangedObjects of an array controller: a given array, appropriately sorted and filtered, not the content array itself! Ok, it was written from the beginning in the documentation, but now it sounds more familiar.
Thank you Stefan, very elegant solution. I wonder how many lines of my code may be avoided if I had the knowledge of cocoa that certain of you have here.
So. Stefan’s NSPredicates are completing Ric’s methods as long as data are in arrays. But there are data shown in a NSOutlineView, which is controlled by a NSTreeController.
The Tree Controller has no methods to install a NSPredicate – and no arrangeObject method. Only sort descriptors to rearrangeObjects(). So, to gather selected items among the tree, I have to do this:
set selectedVerbList to {}
repeat with aNode in theData
repeat with aVerb in aNode's mutableChildNodes()
if aVerb's theState() as integer is 1 then set selectedVerbList to selectedVerbList & (aVerb's representedObject())
end repeat
end repeat
NSTreeController inherits from NSObjectController which has indeed a method to set a predicate.
But as the data model of NSTreeController could be a nested list, the predicate is supposed to be used with Core Data
Since this was originally about an array of checkboxes, and my perils stems from a matrix of checkboxes (NSButtonCell), I feel free to share my experiences, hopefully someone finds them enlightening. Which is the purpose of writing this post.
I had an object full of booleans, that I wanted to bind to a matrix of checkboxes in a control, and tried to work it out with an objectController.
The correct solution was to bind every single button cell up to the file’s owner’s containerObject’s property.
In english: My object with proerties is an object of File’s owner, I bind each property using the full keypath (object.property) the respective button cell in the matrix.
I had to click some and copy and paste some, but it is a much cleaner solution than anything else.
My second experience, is that in order to get to bind the selection of an ArrayController, say to another controller in your view, then you have to bind the selection index of its contents to the Array controller first. Only then can you use the selection of the ArrayController.
I hope that it makes sense, I haven’t seen this spelled out properly anywhere. Maybe Stefan has.