rsharma
rsharma

Reputation: 642

Sorting a NSPopUpButton

I have a list of elements which needs to be displayed in a NSPopUpButton. I want the PopUp button to display the list in alphabetically sorted order. Moreover, there is a NEW MenuItem in the list which would pick up a string from a text field and insert it into the PopUp button. So, there are two questions I would like to ask:

  1. What is the correct way of displaying a list of elements in sorted order in a NSPopUpButton?
  2. How should I handle the insertion of new element to NSPopUpButton so that the sorting order is maintained?

Upvotes: 4

Views: 1610

Answers (3)

rsharma
rsharma

Reputation: 642

I've managed to sort the popup button using the following bindings:

[arrayController bind:@"contentArray" toObject:self withKeyPath:@"displayElements" options:nil];
[popUpButton bind:@"content" toObject:arrayController withKeyPath:@"arrangedObjects" options:nil];
[popUpButton bind:@"contentValues" toObject:arrayController withKeyPath:@"arrangedObjects.title" options:nil];

Also I have changed the statement in init from:

displayElements = [[NSMutableArray alloc] initWithObjects:@"one",@"two",@"three", nil];

to

displayElements = [[NSMutableArray alloc] initWithObjects:[[NSMenuItem alloc]initWithTitle:@"one" action:nil keyEquivalent:@""],
                                                              [[NSMenuItem alloc]initWithTitle:@"two" action:nil keyEquivalent:@""],
                                                              [[NSMenuItem alloc]initWithTitle:@"three" action:nil keyEquivalent:@""],
                                                              nil];

and the sorting is working just fine.
Now the issue is that if I add a Sub-Menu to any of the menu Items and after that I add a new NSMenuItem to the array controller, the Sub-Menu Previously added disappears as shown in images below:

Before adding New Item:
enter image description here


After adding a new Item:
enter image description here


The Same behavior is being displayed when we sort the elements.
Any ideas on fixing this problem??

Upvotes: 1

Monolo
Monolo

Reputation: 18253

If you use an NSArrayController to populate the popup button (or rather, its menu), then you can just define a sort descriptor to handle the sorting.

Getting an array controller to work with a popup button the first time can be a little tricky, but if you bind the popup button's contents to the array controller's arrangedObjects and then bind the content values to a suitable string property of your model objects, you should be fine.

Then, adding a new item will just be a matter of adding an item through the array controller.

So, in case you are not familiar with the NSArrayController class and bindings, the following is short description of how you can do it in Interface Builder. Assume your elements have a string property called name.

  1. Make a (mutable) array property in your nib file's owner (e.g., the app delegate) to hold your elements.
  2. Create an array controller object in Interface Builder.
  3. Bind its content array to the array property you just created. Bindings can be set in Xcode's Bindings Inspector (Cmd-Opt-7).
  4. Add a popup button to the window.
  5. Set the popup button's content binding to the array controller's arrangedObjects controller key. This is the default option for that binding.
  6. Set the popup button's content value binding to the array controller's arrangedObjects controller key and name model key path. This will give the right text in the menu.
  7. Somewhere in your code, create a sort descriptor and set it on the array controller (with the setSortDescriptors: method). To do this, you might want to define an outlet on the file's owner to hold array controller.
  8. To add a new element to your popup button, use the array controller's add: method.

This should get you going with a minimum of your own code, and a little bit of Interface Builder magic.

For fairly straightforward UI elements, bindings can really save a lot of work. More about them here.

Edited to add an example:

For an example of how this can be done, consider an app with an app delegate that has a data property, an array of dictionaries, with a name property. Notice that this is one level of abstraction deeper than your example, in which the array only holds single strings. Personally, I prefer it that way.

self.data = [NSMutableArray arrayWithObjects:
             [NSDictionary dictionaryWithObjectsAndKeys:@"Flowers", @"name", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Animals", @"name", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Trees", @"name", nil],
             nil];

// Sort the array controller alphabetically by the name property
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];

self.arrayController.sortDescriptors = [NSArray arrayWithObject:sd];

Upvotes: 2

rsharma
rsharma

Reputation: 642

Thanks for the answer @Monolo

I have been able to load the values (which have been stored in NSMutableArray) in the popup from NSArrayController. But I am still stuck with the sorting thing. Below is the code, I am not being able to figure what is wrong with it.
ControllerClass.h

@interface ControllerClass : NSObject{
NSWindow *window;
NSArray *sortDescriptorArray;
NSMutableArray *displayElements;
NSSortDescriptor *sortDescriptor; 
IBOutlet NSArrayController *arrayController;
IBOutlet NSTextField *newItemTextField;
}
@property (readwrite,retain) NSMutableArray *displayElements;
-(id)init;
-(IBAction)AddItem:(id)sender;
@end

============================================================
ControllerClass.m

#import "ControllerClass.h"

@implementation ControllerClass
@synthesize displayElements;
-(id)init
{
    self =[super init];
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayElements" ascending:YES];
    sortDescriptorArray = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [arrayController setSortDescriptors:sortDescriptorArray];
    displayElements = [[NSMutableArray alloc] initWithObjects:@"one",@"two",@"three", nil];
    return self;
}

-(IBAction)AddItem:(id)sender
{
    [arrayController addObject:[newItemTextField stringValue]];
}
@end

Upvotes: 0

Related Questions