Binding a pop menu with multiples values

Hello,

I have a dictionary of objects, each of them having an integer ID and a name. I want to bind this dictionary to a pop menu which would list:

ID1 Name1
.

Now I can fill my pop menu with the arranged objects of my dictionary controller but only with one key (either the ID or the name). Is there a special “format” to have both values in menu items?

Of course I don’t want to do this by code: it would be simple, but I’ll have to synchronize the menu myself.

Regards,

I am pretty sure that the only solution lies in generating another array that you design utilizing both elements, and binding that array to the Popup.

You could bind them to a handler that returns the two fields combined into one. But would it be possible to modify the dictionary so that it has a third field containing what you want anyway?

Thank you for your answers. They make me ask more questions:

1a) the array that concatenates the two fields, or the new field in the dictionary, won’t certainly be automatically updated-- so I’ll have to do it by code. There is no such:

property myField1 : missing value
property myField2 : missing value

property myConcatenation : myField1 & myField2
.

1b) .or maybe I’ll have to make my array/dictionary key an observer of the two other values?

  1. Shane’s solution is OK, in that case I’ll have to update the menu just after it has been clicked and just before it opens? There will certainly be no noticeable delay, but I’ll have to give up the bindings. Sad. I really LIKE the bindings.

What is really missing is the possibility to FORMAT the bindings in IB, a sort of–
Model Key Path : “The value is :” & @myValue & “.” or whatever format defined by Apple.

OK, I’ll try to do with what’s here instead of regretting what’s missing.

Regards,

Where is the dictionary coming from?

The dictionary is a record-equivalent which is created, deleted, read/written from/to file. When created by user, it is initialized from an AS record (a property) and then stored in a NSMutableArray by an NSArrayController.

Meanwhile I was looking at :
NSKeyValueBindingCreation Protocol Reference
which has a lot of options. There is a possibility to bind multiple values (the example is “%{value1}@ of %{value2}@ selected”.) but alas it does not seem to apply to menu items.

What do you mean by “created by user”? I guess I don’t understand why you dictionary can’t be created with a third field, but then you’re not really explaining where it comes from. Phrases like “is created” are more than a bit vague…

Shane,

This is the record of a “card” (an entity which encapsulates multiple visual elements):

And this is the creation of a new “card”:

    on createNewCard(withID)
        set theFile to (withID as string) & ".atxt" -- file name is (for example): "22.atxt"
        set cID of cRecord to withID as integer -- init ID
        set cName of cRecord to gProposedCardName as string -- init name
        set cBkColor of cRecord to NSColor's whiteColor() -- init background color
        NSKeyedArchiver's archiveRootObject_toFile_(cRecord, theFile) -- card is created on disk
        my gCardController's addObject_(cRecord) -- and added to the controller
    end

Is it more clear to you? What I wish to do is to bind the cID and cName together in the pop menu items.

You don’t need to do anything complicated, just add one line:
set cPopupTitle of cRecord to cID as string & " " & cName

Ric

Hello Ric!

.and do it again when the user deletes the card, or rename it. In one word, I’ll have to replace everything the bindings were doing for me. And not to forget that the user renames the card via the bindings. that is, from the code’s point of view, behind the scenes.

Of course this is not complicated – it’s just a question of choice between coding and binding. As the pop menu is just one single option that the user COULD choose, it’s certainly better to build this menu on the fly if needed.

Regards,

Do you really need to do anything extra if the user deletes a card? Don’t you just delete the whole cRecord?

As for renaming, I don’t think you need to replace everything the bindings were doing. I think you could probably do it by overriding the setter for cName – when the user renames, I’m presuming through an interface element, the setter should get called, and you could redefine the cPopupTitle at that point. It would only be a few lines of code. I’d have to think a little about exactly how to do that since cName is embedded in the cRecord, but I’m pretty sure it should be doable.

Ric

You may have to implement a few more methods. Basically, you need to look at the Key Value Programming Guide, and its section on accessor methods for to-many relationships.

You will possibly need countOf, objectInAtIndex: and AtIndexes:, and it sounds like you’re allowing changes via bindings, so also insert:atIndexes:, removeAtIndexes:, and possibly replaceAtIndexes:with:.

If you add a cPopupTitle field as Ric said, they’d be very simple methods. You’d just need to make replaceAtIndexes:with: look at the values for cID and cName, and modify the value set for cPopupTitle accordingly.

It’s possible that replaceAtIndexes:with: alone will solve your problem, so I’d start with that. And if you need the others too, well the docs suggest you’ll get much better performance.

I have another mystery to solve:

I bind tentatively my popup menu with the cID key only, by Content Values. The popup is filled with my integer values and perfectly updated when a new ID is created or deleted. As an ID cannot be changed once created, it’s convenient to do so, because the popup has to retain a preferred ID.

I’m storing this preferred ID into a key of another record, let’s say the “startID” key.

So I bind this startID to the Selected Value of the popup menu. So far, so good.

But if I modify the Content Value of the popup menu (for example if I delete one ID in the list), the Selected Value is lost.

How is it possible? Did I misunderstand the role of “Content Values” and “Selected Value”?

The selected value is the selected object. If you select something and delete it, you usually end up with no selected object.

Shane, I read the docs and honestly, my neurons are just overloaded. I didn’t realize it was so complicated to concatenate an integer and a string. I suppose it was not the kind of things that Apple had foreseen. :confused:

Shane,

Of course it’s the correct behavior… if you delete the selection! But in my case, if I select 14 and delete 31, the value 14 is lost too. :slight_smile:

After edit: Everything is OK, I’ve just fallen into the same trap as before: bindings don’t work with AS records – as soon as I replaced it with a NSMutableDictionary, it behaves correctly.

It’s not that – what you’re trying to do is override normal bindings behavior, and to do that you need to find which part you can override to get what you want. Binding into an array of mutable dictionaries is a fairly complicated thing to do, under the hood.

Add a cPopupTitle value to your cRecord items. Then add this handler, replacing with the name of the property that holds the array of records (capping its first letter):

on replaceObjectInAtIndex_withObject_(n,newValue)
– modify the new value
set theID to newValue’s valueForKey_(“cID”)
set theName to newValue’s valueForKey_(“cName”)
set newCPopupTitle to (theID as text) & space & theName
newValue’s setValue_forKey_(newCPopupTitle, “cPopupTitle”)
– replace old entry
's replaceObjectAtIndex_withObject_(n, newValue)
end

That’s untested, but it should be pretty close to what you need.

Shane,

Three questions:

  1. my property for the list (NSArray) of records (NSDictionaries) is called gCardList. Does the handler’s name become [b]replaceObjectInGCardListAtIndex_withObject_/b ?

  2. does what you call become gCardList’s replaceObjectAtIndex_withObject_(n, newValue)

  3. when is this handler supposed to be called? I never get it fired.

Regards,

For 1 and 2, yes. For 3, I thought whenever you change cName via a binding. Do you have Handle as Compound Value checked? I’m assuming not. Have you put a log call in it to see it’s not happening? The code as I wrote will probably fail because it tries to modify an immutable dictionary, so you might need to make a mutable dictionary from newValue before it tries to make the change.

Assuming you have a mutable array of immutable dictionaries, the only way bindings can change an entry is to replace it – bindings have no magic powers. (If you have Handle as Compound Value checked, it replaces the whole array.) So what this code does is override the KVC method it should call to do so.

It’s possible that the lack of the other methods that I mentioned is the problem – perhaps you need them as well.

Try adding an implementation of objectInAtIndex: something like this pseudo code:

on ObjectInGCardListAtIndex(n)
get the nth entry
change it to a mutable dictionary
change its newCPopupTitle value to be correct
return the new dictionary
end