0

There seem to be dozens of questions about binding a NSPopupButton, so I feel a little better about struggling so much with this, but none of them seem to fix my issue.

I have an NSManagedObject subclass that has a one to many relationship with another class. Let's say Foo has a relationship to Bar, so Foo has a property that is an NSSet of Bars.

I have created an NSArrayController and bound its contentSet to the Foo object and the 'bars' key path.

I then bind my NSPopupButton's content to the array controller's arrangedObjects and its contentValues to the array controller's arrangedObject (controller key) and 'name' (the property on Bar that I want to display).

This all works fine so far, but when I try to bind the NSPopupButton's selected object to the array controller's selection The NSPopupButton displays "<_NSArrayControllerObjectProxy". I also tried adding 'name' as the keyPath for this binding, and this does make the NSPopupButton display the name correctly, but then when I change the selection in the popup the app thrown an exception:

Unacceptable type of value for attribute: property = "name"; desired type = NSString; given type = Bar;

I guess this makes sense, as the popup is trying to set the string value of 'name' as the selected Bar. I would think I would therefore need to bind a selected object and a selected value, but the XIB will disable selected value if I have a selected object set.

I have also tried binding the selected value instead, and this half works (the array controller's selection does change) but the options in the popupmenu don't change to show the one that was deselected and hide the newly selected one).

Failing all of this I read an article here: http://blog.chrisblunt.com/cocoa-bindings-and-nspopupbutton/ that says NSPopupButton "NSPopUpButton does not record the user’s selection" and to instead store your selection somewhere other than the array controller. I tried putting a currentBar property in my window and binding the selection to that instead, and although I can see that currentBar is changing (because I have another view bound to it also) the label in the popup button does not change.

If anyone can help me out I'd be very appreciative.

DownUnder
  • 23
  • 4

1 Answers1

0

The FooBar thing confuses me so here is my example based on real world objects.

ExpenseTransaction has attributes (date, trxDescription, category, amount).

Category has a single attribute (name)

ExpenseTransaction.category is a To-One relationship to Category (in other words a transaction can belong to only one Category).

Category.transactions is a To-Many relationship to ExpenseTransaction (in other words many transactions can belong to the same Category).

The UI for creating a new transaction or editing and existing one uses NSPopupButton to display the list of available Categories using the name attribute. For existing transactions the popup will display the selected transactions category.

Bindings for the Category popup are as follows:

Content (Category.arrangedObjects)

Content Objects (Category.arrangedObjects) - we want to link to the actual category not its name because the attribute is a relationship not a string value

Content Values (Category.arrangedObjects.name) - we want the name to be displayed in the popup list

Selected Object (ExpenseTransaction.selection.category)

Using your FooBar analogy:

Category has a relationship to ExpenseTransaction, so Category has a property (transactions) that is a NSSet of ExpenseTransactions. Now this is pretty much the inverse of my arrangement so I don't really know how or why you would populate the popup with ExpenseTransaction objects because only one selected item in the popup could be related to the Category object when in fact you need the whole set to be related. However the other way around works just fine because the popup would contain a list of all the Foo items and so whenever you select a Bar item the corresponding Foo item could be selected from the popup.

Hope this makes sense.

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
  • My use case is a little different to yours. I'm using the popup button simply to select which top level object is visible (called Bars in my confusing example) and I have created the Foo object merely to have an object that has a collection of all the Bars so I have something to bind an array controller to. What I have been trying to do is bind the popup to the selection in the arrangedObjects, as that's all it really is - a selection. Do you think it's because I need to store the selection onto a model that has an inverse like yours does? – DownUnder Nov 11 '13 at 11:35
  • I tried binding the selection to a property in the window (given the selection is more to do with the view than the model) but while the popup does change what that property points to the popoup itself always says "no value". – DownUnder Nov 11 '13 at 11:37
  • Still not sure I fully understand what your UI is trying to do but you may want to create two arrayControllers, FooArray and BarArray. Set both to Entity Mode and set the Entity names to Foo and Bar respectively. Now to ensure the BarArray contains only the items related to the selected item in FooArray set the BarArray Content Set binding to (FooArray.selection.bars) where bars is the To-Many relationship (property). Now the BarArray should only ever contain the subset of Bars related to the selected Foo item. – Duncan Groenewald Nov 11 '13 at 22:16
  • What I don't understand is why you are binding the popup's selected item to anything because there seems to be no corresponding relationship in your model. What is the meaning of the selected item in the popup ? Or put another way what will the user achieve by selecting an item from the popup which contains all the Bar items belonging to the somehow(?) selected Foo item. How is the Foo item selected ? – Duncan Groenewald Nov 11 '13 at 22:17
  • Reading your comments above again it seems the only reason you have the Foo object is so you can bind an array controller to it. Why can't you simply create an ArrayController and set its entity to Bars - that way it will contain all the Bars in your database. You don't need any other bindings to set up the BarsArrayController. And I am assuming Bars is an entity in the database (or a managed object). Then bind the popup to the BarsArrayController and bind an instance variable to the popups selectedObject. – Duncan Groenewald Nov 11 '13 at 22:40
  • Thank you. I've certainly learned a few things from you. I didn't know about that "entity mode" where you can make the array controller contain all of the entities of that type. I can't find anything in Apple's docs about that. I have now set it up in that way and the list of items is correct, and it does change the value of my instance variable, but the label of the popupbutton still doesn't update to reflect the change. It seems to only work in one direction. – DownUnder Nov 12 '13 at 05:24
  • When you say the label does not change to reflect the change what do you mean? The user selecting an item in the popup list ? What are the two directions you expect it to work in ? Also remember to bind the popups Content Value to the Bar.name attribute or whatever you want displayed in the list and the Content Objects to arrangedObjects as this will bind the Bar object to the ivar. – Duncan Groenewald Nov 13 '13 at 00:52
  • If the "other way" means that by changing the ivar you want the popup to show the new ivar value by automatically selecting the correct item from the popup list then this should work as long as you are using KVC compliant calls. I am pretty sure the bindings should take care of that for you. From memory a View has what is called a representedObject and its purpose is for attaching the object you are representing in the View. So it might be best to create an ObjectController in IB which would represent the selected Bar. – Duncan Groenewald Nov 13 '13 at 01:01
  • In IB you would bind the objectController to your ivar and then bind properties of the objectController to the UI fields. You could probably bind the objectController directly to the popup.selectedObject if you wanted to. Anyway if you provided some screen shots and the object model I could probably help more easily. – Duncan Groenewald Nov 13 '13 at 01:02