Objective C, Cocoa, Interface Builder, and NSPopUpButtons
|
|
Thread rating:  |
Don Bruder - 11 Jan 2008 02:35 GMT Over Xmas, I started delving into the mysteries of Objective C and Cocoa. For the record, I'm still (and will continue to be until finances improve) running 10.3.9, with the newest-that-will-run-on-it (XCode 1.5) developer tools.
It's been basically fairly pleasant, so far - I'm finding that Obj-C actually "makes sense", even though I've been left wondering "what happened?!?!?" in every previous encounter I've had with OOP languages like C++.
I've had success with subclassing/extending various Cocoa classes, and gotten some pretty good results, but I've hit a seemingly insurmountable obstacle when trying to "talk to" IB-created UIs.
I've been beating on a variation on the Obj-C tutorial's "Currency Converter" program. I'm wanting to turn it "universal" and "realtime", in that it will go out to a web site, retrieve current conversion rates, parse the returned data to extract currency names and exchange rates, then use those to do the conversion. The "go get the data" and "parse it" portions are working exactly as they should be - I end up with, exactly as planned, an NSMutableArray stuffed full of custom "CurrencyValueItem" objects (with associated get/set/play with/etc methods) that hold the names and "units of this currency per dollar" and "dollars per unit of this currency" values for a variable number of currencies. In the debugger, manually setting up variables then letting things fall through into calling the convert method is doing fine. Displaying the result likewise works great.
Where I'm hitting trouble is letting the user select what currency to convert from/to, and based on the user's selections, doing the correct conversion.
The way I've got things laid out in Interface Builder, I have an NSTextfield for input of the "number of currency units". Following that, I have what seems the most sensible method of selecting the "source currency" - an NSPopUpButton, then another NSPopUpButton to select the "target currency", and finally, another NSTextfield to accept the converted value from the converter controller function. And, of course, there's a "Make it happen" button to kick the whole thing into action once the user has plugged in the value to convert and selected what the conversion should be.
This is where I hit my "real trouble" -
How do I set up and use the menus???
More specifically, I can't figure out how to tell what menu I'm working with. Putting them together in Interface Builder is easy enough, but trying to make two separate instances of NSPopUpButton objects so that they're independently "tinker-with-able" within the program code eludes me.
I'm not having trouble populating the menus, or retrieving the index of the user-selected item - that's quite simple. What's difficult is figuring out which menu I'm talking to.
I *COULD*, I guess, build the currency info into the nib file, but that would rather defeat the purpose of a "live" read of the data from the website.
The documentation for NSPopUpButton is adequate, as far as it goes, but nothing I've spotted addresses the interplay between the NSPopUpButton class and the IB-created button that it should be talking to/using in a way that lets me accomplish what I'm trying to do.
Suggestions?
 Signature Don Bruder - dakidd@sonic.net - If your "From:" address isn't on my whitelist, or the subject of the message doesn't contain the exact text "PopperAndShadow" somewhere, any message sent to this address will go in the garbage without my ever knowing it arrived. Sorry... <http://www.sonic.net/~dakidd> for more info
Dave Seaman - 11 Jan 2008 05:01 GMT > Over Xmas, I started delving into the mysteries of Objective C and > Cocoa. For the record, I'm still (and will continue to be until finances > improve) running 10.3.9, with the newest-that-will-run-on-it (XCode 1.5) > developer tools.
> It's been basically fairly pleasant, so far - I'm finding that Obj-C > actually "makes sense", even though I've been left wondering "what > happened?!?!?" in every previous encounter I've had with OOP languages > like C++.
> I've had success with subclassing/extending various Cocoa classes, and > gotten some pretty good results, but I've hit a seemingly insurmountable > obstacle when trying to "talk to" IB-created UIs. You can solve these problems by using outlets and actions, which can be set up in Interface Builder.
> I've been beating on a variation on the Obj-C tutorial's "Currency > Converter" program. I'm wanting to turn it "universal" and "realtime", [quoted text clipped - 9 lines] > things fall through into calling the convert method is doing fine. > Displaying the result likewise works great.
> Where I'm hitting trouble is letting the user select what currency to > convert from/to, and based on the user's selections, doing the correct > conversion.
> The way I've got things laid out in Interface Builder, I have an > NSTextfield for input of the "number of currency units". Following that, [quoted text clipped - 5 lines] > once the user has plugged in the value to convert and selected what the > conversion should be.
> This is where I hit my "real trouble" -
> How do I set up and use the menus??? If you mean you want to do that programmatically, you should make each NSPopUpButton an outlet of your controller class. Then you can send appropriate messages to each one. For example, there are methods called addItemWithTitle: and addItemsWithTitles:, plus methods for removing items and so on.
You might choose to adopt a scheme like this:
// myController
#import <Cocoa/Cocoa.h>
@interface myController : NSObject { IBOutlet NSTextField *numberOfCurrencyUnits; IBOutlet NSPopUpButton *sourceCurrencyMenu; IBOutlet NSPopUpButton *targetCurrencyMenu; IBOutlet NSTextField *convertedValue; // other instance variables here }
// method declarations -- actions are discussed below: - (IBAction)sourceCurrencyDidChange:(id)sender; - (IBAction)targetCurrencyDidChange:(id)sender; - (IBAction)doTheConversion:(id)sender; // other methods, as needed
@end
Based on your description, you probably already have a controller object instantiated in your nib file, you have at least the NSTextFields set up as outlets and you should already have at least the skeleton of an action method to be invoked by the pushbutton. All these things need to be connected appropriately in Interface Builder.
You need to add the popups as additional outlets, so you can talk to them and presumably populate them at runtime. If you haven't created the source files from your controller yet, you can do this from Interface Builder. If you already have generated source files, it may be easier to edit them to include the IBOutlets and IBActions shown above, and then drag the icon of the source file from your project window into the nib file's window, to let Interface Builder know about the new outlets and actions. Then make the necessary connections.
You can take a passive approach and just query each menu to see what the current selection is when you need it. If you do that, you probably won't use the menu actions sourceCurrencyDidChange: and targetCurrencyDidChange: that I indicated. However, the point of these action methods is that you can process a change in the state of a menu immediately when it happens and then preserve that information for the next time you need to process a conversion, instead of querying each menu again for each transaction.
> More specifically, I can't figure out how to tell what menu I'm working > with. Putting them together in Interface Builder is easy enough, but > trying to make two separate instances of NSPopUpButton objects so that > they're independently "tinker-with-able" within the program code eludes > me. It's no problem if each popup is an outlet, as shown above.
If you are using the action methods, you need to control-drag from each menu back to the controller object (as the target) and select the desired action in the inspector.
Your implementations might be something along the lines of
- (IBAction)sourceCurrencyDidChange:(id)sender { NSString *newCurrency = [sender titleOfSelectedItem]; // do what needs to be done with the new currency }
This is just an example. You may be more interested in getting the index of the selected item instead of the title, by using indexOfSelectedItem.
> I'm not having trouble populating the menus, or retrieving the index of > the user-selected item - that's quite simple. What's difficult is > figuring out which menu I'm talking to. When a control sends an action message to its designated target, it always sends itself as an argument with the message. This means you know who sent the message and you can go back to get more information about the state of the sender by sending messages back to it.
> I *COULD*, I guess, build the currency info into the nib file, but that > would rather defeat the purpose of a "live" read of the data from the > website. That's a separate problem, solved by making each popup an outlet.
> The documentation for NSPopUpButton is adequate, as far as it goes, but > nothing I've spotted addresses the interplay between the NSPopUpButton > class and the IB-created button that it should be talking to/using in a > way that lets me accomplish what I'm trying to do.
> Suggestions? Using outlets, you can populate the menus dynamically at runtime. That doesn't stop you from setting up targets/actions in Interface Builder, since those are probably static for this application.
 Signature Dave Seaman Oral Arguments in Mumia Abu-Jamal Case heard May 17 U.S. Court of Appeals, Third Circuit <http://www.abu-jamal-news.com/>
Don Bruder - 11 Jan 2008 10:02 GMT <snip for space>
(First whack at some thoughts that you've given me, done at 2 in the AM local time... I'll actually try some of them when daylight comes around again...)
> > This is where I hit my "real trouble" - > > > How do I set up and use the menus??? > > If you mean you want to do that programmatically, Yep, that's exactly what I want to do. At each run, the first thing that needs to happen is making a run to the website to harvest the currency names and conversion values into my NSMutableArray of CurrencyItem object (which in turn are subclassed from NSObject).
As mentioned, that "run for the data" operation is working exactly as planned - I was absolutely shocked to get the guts of it right the very first try. The "cosmetics" of data-manipulation that went into it needed a couple of minor tweaks - as in "No, stupid, you don't get the currency name by reading to this byte of the stream, you have to read until this OTHER byte", and so on)
Once thats completed, I've got all the data I need to populate the menus. Doing the actual populating is a simple matter - Iterate through the NSMutableArray grabbing the currency name, and handing it off to the NSPopUpButton to be added. (Which, incidentally, means that the menus are in the same order as the array, which makes life easier - I think the method is "getIndexOfSelectedItem", and presto - I've got the index of the NSMutableArray item I need to retrieve to do the calculation)
> you should make each > NSPopUpButton an outlet of your controller class. Doesn't that run into the problem of "The menus need to be populated before the controller gets a chance to run, therefore, the outlets need to be part of some other class - But which one???"
Or am I making a rookie mistake? (I suspect I might be - after all, I *AM* a rookie!)
> Then you can send > appropriate messages to each one. For example, there are methods called > addItemWithTitle: and addItemsWithTitles:, plus methods for removing > items and so on. Right - That part was pretty simple to figure out. Or would have been, if it weren't for the "But where should I be sending the messages???" concept... My first stab at it was in the form of class methods grafted onto the NSPopUpButton class, but that failed miserably (Well, actually, it worked, but left me with absolutely no clue which menu I had just populated, and seemingly no way to find out - and no way to say "[Menu"X" getIndexOfSelectedItem] so that I could retrieve the data to do the conversion)
> You might choose to adopt a scheme like this: > [quoted text clipped - 9 lines] > // other instance variables here > } OK, that follows reasonably, but leaves me in a bit of a bind - Unless I've missed (or misunderstood) something - all too possible since I'm so completely green when it comes to doing things "The Objective C way" - the myController object doesn't actually "do anything" until it gets an action passed to it. Which means that until sometime after the first pass through the controller (by whatever means that pass is caused to happen...) my menus are both full of the default stuff that IB puts there as placeholders.
I need to get a grip on the menus before the UI even becomes visible, let alone has an action happen that makes the controller run, so I can populate the menus. Or do I? Of COURSE I do... Don't I?
I looked at the awakeFromNib method, but that didn't get me anywhere except frustrated. It looked *REALLY* promising at first, but as I dug deeper, <pfffft> that idea went out the window. Or perhaps I need an awakeFromNib method on/in the controller class itself? Something that says "Oh, look! Here's an NSPopUpButton that needs to be populated! Call somebody to do it!", perhaps?
> // method declarations -- actions are discussed below: > - (IBAction)sourceCurrencyDidChange:(id)sender; [quoted text clipped - 9 lines] > method to be invoked by the pushbutton. All these things need to be > connected appropriately in Interface Builder. Right, that's covered. Took some twiddling based on the original Currency Converter demo, but I figured out how to do the hookups for the NSTextFields, and the action from the "do it" button so that things were operational.
> You need to add the popups as additional outlets, so you can talk to them > and presumably populate them at runtime. Hmmm... But again, it (seems like it) runs into the problem of "can't populate without knowing the object that needs to be populated, and can't know that until the controller fires up".
> If you haven't created the > source files from your controller yet, you can do this from Interface [quoted text clipped - 3 lines] > file's window, to let Interface Builder know about the new outlets and > actions. <blink blink> Now THERE is something that hadn't dawned on me! I wasn't aware that it was possible to take "code" and cram it into the nib file/IB. This may be the key I've been missing...
> Then make the necessary connections. As if my code were something that already exists in IB's "I know how to do this" bag of tricks... Yeah, That definitely puts me on a new way of thinking...
> You can take a passive approach and just query each menu to see what the > current selection is when you need it. If you do that, you probably [quoted text clipped - 4 lines] > next time you need to process a conversion, instead of querying each menu > again for each transaction. The passive approach you describe is, or so it seems to me, the only viable way to do anything with this particular "mess" of mine.
> > More specifically, I can't figure out how to tell what menu I'm working > > with. Putting them together in Interface Builder is easy enough, but [quoted text clipped - 43 lines] > doesn't stop you from setting up targets/actions in Interface Builder, > since those are probably static for this application.
 Signature Don Bruder - dakidd@sonic.net - If your "From:" address isn't on my whitelist, or the subject of the message doesn't contain the exact text "PopperAndShadow" somewhere, any message sent to this address will go in the garbage without my ever knowing it arrived. Sorry... <http://www.sonic.net/~dakidd> for more info
Gregory Weston - 11 Jan 2008 13:43 GMT > <snip for space> > [quoted text clipped - 37 lines] > Or am I making a rookie mistake? (I suspect I might be - after all, I > *AM* a rookie!) Rookie mistake. Your controller can do things besides gather the input data and calculate the result. There are several methods that you can define that will be invoked at well-defined times before the user has any chance to interact with your UI. How/when are you grabbing the units and conversion rates? You should be able to populate the menus (since they'll be static for any given run, right?) right then - as long as you've defined the outlets in IB and in your source file such that your controller has a reference to the controls.
> I looked at the awakeFromNib method, but that didn't get me anywhere > except frustrated. It looked *REALLY* promising at first, but as I dug > deeper, <pfffft> that idea went out the window. Or perhaps I need an > awakeFromNib method on/in the controller class itself? Something that > says "Oh, look! Here's an NSPopUpButton that needs to be populated! Call > somebody to do it!", perhaps? Ding!
> > If you haven't created the > > source files from your controller yet, you can do this from Interface [quoted text clipped - 7 lines] > aware that it was possible to take "code" and cram it into the nib > file/IB. This may be the key I've been missing... It's documented, but not obvious. As a complement to IB's ability to generate stub source files, it can also *read* header files and create/update class definitions based on what's in them. You just make sure you've got the instance variables you care about tagged as IBOutlet, and make sure actions have a return to of (IBAction) and take a single argument typed as 'id'.
You can also do it manually, of course. You just have to take care to make sure you type the same name into IB that you did in the header file.
This goes away in XCode 3...IB is more tightly integrated with XCode and reads the header files on the fly without effort on your part.
G
Don Bruder - 11 Jan 2008 23:25 GMT > > Or am I making a rookie mistake? (I suspect I might be - after all, I > > *AM* a rookie!) [quoted text clipped - 4 lines] > any chance to interact with your UI. How/when are you grabbing the units > and conversion rates? I've tried various times/ways of doing it, but none of them seemed to be "right" - The data got grabbed and parsed, but where/how I did it just didn't seem to be "correct", and once grabbed, I couldn't figure out what to do with it next.
> You should be able to populate the menus (since > they'll be static for any given run, right?) I expect that most of the time, the answer would be yes, they're static. Although I can see the possibility that the app sits idle for long enough periods for the conversion values (though probably not the currency names) to fluctuate significantly.
> right then - as long as > you've defined the outlets in IB and in your source file such that your > controller has a reference to the controls. OK, let me bounce some code off you - maybe I understand what you're saying, maybe I don't. You tell me which is the case!
(I'm deliberately not putting in "boilerplate" stuff to save space. Assume it's there - in my local code, it is)
CurrencyItem.h
@interface CurrencyItem : NSObject { NSString *currencyName; float dollarsPerUnit; float unitsPerDollar; }
// Return a new instance of CurrencyItem, inited with the values // passed in. + (CurrencyItem *)newWithData:(NSString *)theName dollarsPerUnit:(float)dpu unitsPerDollar:(float)upd;
// Data getters - (NSString *)name; - (float)dollarsPerUnit; - (float)unitsPerDollar;
// Data setters - (void)setName:(NSString *)theName; - (void)setDollarsPerUnit:(float)theValue; - (void)setUnitsPerDollar:(float)theValue; @end
RateSource.h
@interface RateSource : NSObject { BOOL Initialized; NSMutableData *Buffer; NSString *TheDate; }
// Create and return an initialized, but empty, RateSource object. + (RateSource *)New;
// Finalize initializing a RateSource instance. - (RateSource *)init;
// Fill the array passed to us with CurrencyItem objects parsed from // the data we get from the website. Return the timestamp for the data // as an NSString. - (NSString *)fillArray:(NSMutableArray *)TheArray;
// Many lines of irrelevant (to this question) instance methods // involved with parsing the HTML the website returns, building // CurrencyItem objects using the parsed data, and stuffing those // objects into the array elided to save space. @end
Given these two interfaces, it *SOUNDS LIKE* you're saying I need my controller to look similar to this:
MyController.h
@interface MyController:NSObject { // various other (not relevant to this question) instance variables
IBOutlet NSPopUpButton *Menu1; IBOutlet NSPopUpButton *Menu2; NSMutableArray *myArray; }
// various irrelevant methods elided
- (void) awakeFromNib; @end
MyController.m
@implementation myController
// other methods...
- (void)awakeFromNib { CFIndex counter; RateSource *rateSource = [RateSource New];
// Perhaps much "other stuff", eventually leading to... myArray = [NSMutableArray arrayWithCapacity:0]; // Get the data from the website dataTimestamp = [rateSource fillArray:myArray]; // Now I think I need to populate the menus with the data // that RateSource stuffed into myArray - right? // Here's where I get confused... // How do I instantiate "Menu1" and "Menu2"? // Or do they already exist as ready-to-use objects when I // reach this point? // I'll assume that they do indeed exist, and that // I can simply go to work on them. If that's not the case, // PLEASE clue me as to how to "make" them. [Menu1 removeAllItems]; // Empty out IB's placeholder items [Menu2 removeAllItems]; // ditto for (counter = 0; counter < [myArray count]; counter++) { [Menu1 addItemWithTitle:[[myArray objectAtIndex:counter] name]]; [Menu2 addItemWithTitle:[[myArray objectAtIndex:counter] name]]; } } @end
> > I looked at the awakeFromNib method, but that didn't get me anywhere > > except frustrated. It looked *REALLY* promising at first, but as I dug [quoted text clipped - 4 lines] > > Ding! "Ding" indeed... I'm thinking that the code above should do it - *IF* I'm understanding what you've said correctly. Time for some testing...
(Time passes)
Uh-oh...
I've managed to break SOMETHING, it looks like. Got everything to compile and link with zero warnings or errors, however, trying to run the program to see if it's working does *ABSOLUTELY NOTHING*. No bouncing icon in the dock, no earth-shattering kaboom, no nothing.
<sigh> I wonder what I've got wrong this time?
Back to the drawing board... Wuddayawannabet I mangled something while re-creating the project that I foolishly trashed due to utter frustration?
 Signature Don Bruder - dakidd@sonic.net - If your "From:" address isn't on my whitelist, or the subject of the message doesn't contain the exact text "PopperAndShadow" somewhere, any message sent to this address will go in the garbage without my ever knowing it arrived. Sorry... <http://www.sonic.net/~dakidd> for more info
Dave Seaman - 11 Jan 2008 14:01 GMT ><snip for space>
> (First whack at some thoughts that you've given me, done at 2 in the AM > local time... I'll actually try some of them when daylight comes around > again...)
>> > This is where I hit my "real trouble" -
>> > How do I set up and use the menus???
>> If you mean you want to do that programmatically, [snip comments]
>> you should make each >> NSPopUpButton an outlet of your controller class.
> Doesn't that run into the problem of "The menus need to be populated > before the controller gets a chance to run, therefore, the outlets need > to be part of some other class - But which one???"
> Or am I making a rookie mistake? (I suspect I might be - after all, I > *AM* a rookie!) There's no problem. You would populate the menus in your controller's awakeFromNib method.
>> Then you can send >> appropriate messages to each one. For example, there are methods called >> addItemWithTitle: and addItemsWithTitles:, plus methods for removing >> items and so on.
> Right - That part was pretty simple to figure out. Or would have been, > if it weren't for the "But where should I be sending the messages???" To the outlet in your controller class, that you connected to the popup in IB.
> concept... My first stab at it was in the form of class methods grafted > onto the NSPopUpButton class, but that failed miserably (Well, actually, > it worked, but left me with absolutely no clue which menu I had just > populated, and seemingly no way to find out - and no way to say > "[Menu"X" getIndexOfSelectedItem] so that I could retrieve the data to > do the conversion)
>> You might choose to adopt a scheme like this:
>> // myController
>> #import <Cocoa/Cocoa.h>
>> @interface myController : NSObject { >> IBOutlet NSTextField *numberOfCurrencyUnits; [quoted text clipped - 3 lines] >> // other instance variables here >> }
> OK, that follows reasonably, but leaves me in a bit of a bind - Unless > I've missed (or misunderstood) something - all too possible since I'm so > completely green when it comes to doing things "The Objective C way" - > the myController object doesn't actually "do anything" until it gets an > action passed to it. After the objects in your nib file have been unarchived and all the outlets and actions connected as specified by IB, each object receives the awakeFromNib message. You should implement that method in your controller class and use it to populate your popup menus. You can't do that in the controller's init method, because the IB connections haven't been set up yet when init runs. That's why we have the awakeFromNib method.
> Which means that until sometime after the first > pass through the controller (by whatever means that pass is caused to > happen...) my menus are both full of the default stuff that IB puts > there as placeholders. Your controller doesn't run all at once. It has a bunch of methods, each of which runs when the corresponding message is received. Your awakeFromNib method runs before any of your action methods, and it can remove whatever IB put in the menu before adding new items.
> I need to get a grip on the menus before the UI even becomes visible, > let alone has an action happen that makes the controller run, so I can > populate the menus. Or do I? Of COURSE I do... Don't I?
> I looked at the awakeFromNib method, but that didn't get me anywhere > except frustrated. It looked *REALLY* promising at first, but as I dug > deeper, <pfffft> that idea went out the window. Or perhaps I need an > awakeFromNib method on/in the controller class itself? Bingo.
>> If you haven't created the >> source files from your controller yet, you can do this from Interface [quoted text clipped - 3 lines] >> file's window, to let Interface Builder know about the new outlets and >> actions.
><blink blink> Now THERE is something that hadn't dawned on me! I wasn't > aware that it was possible to take "code" and cram it into the nib > file/IB. This may be the key I've been missing...
>> Then make the necessary connections.
> As if my code were something that already exists in IB's "I know how to > do this" bag of tricks... Yeah, That definitely puts me on a new way of > thinking... In fact, I never create outlets or actions in IB anymore. I always create new class files in Xcode instead of IB. In Xcode 3.0, you don't even need to drag the header file to the nib window, because synchronization is automatic between Xcode and IB.
>> You can take a passive approach and just query each menu to see what the >> current selection is when you need it. If you do that, you probably [quoted text clipped - 4 lines] >> next time you need to process a conversion, instead of querying each menu >> again for each transaction.
> The passive approach you describe is, or so it seems to me, the only > viable way to do anything with this particular "mess" of mine. You could implement it that way first, and consider putting in action methods later in order to learn how to do that.
And, for the distant future, I'll just mention "Cocoa Bindings". This is another way to keep things in sync, and it requires less code than the target/action approach. In fact, in the developer examples you can find a version of the Currency Converter application that uses Cocoa Bindings. One of the visible results of doing it this way is that you no longer need a button marked "Do It". The result field gets automatically updated whenever you change either of the input fields.
 Signature Dave Seaman Oral Arguments in Mumia Abu-Jamal Case heard May 17 U.S. Court of Appeals, Third Circuit <http://www.abu-jamal-news.com/>
Don Bruder - 12 Jan 2008 01:21 GMT > > Doesn't that run into the problem of "The menus need to be populated > > before the controller gets a chance to run, therefore, the outlets need [quoted text clipped - 5 lines] > There's no problem. You would populate the menus in your controller's > awakeFromNib method. Greg managed to beat that into my thick skull :) I think I've got it, now. (and hopefully "forever"!)
> To the outlet in your controller class, that you connected to the popup > in IB. Except that, at the time I wrote that, I didn't quite grok the "add an outlet" part.
> > I looked at the awakeFromNib method, but that didn't get me anywhere > > except frustrated. It looked *REALLY* promising at first, but as I dug > > deeper, <pfffft> that idea went out the window. Or perhaps I need an > > awakeFromNib method on/in the controller class itself? > > Bingo. Between you and Greg "double-teaming" me with it, that concept has finally penetrated. And it works. Hooray! :)
> In fact, I never create outlets or actions in IB anymore. I always > create new class files in Xcode instead of IB. In Xcode 3.0, you don't > even need to drag the header file to the nib window, because > synchronization is automatic between Xcode and IB. A little beyond me right now, I'm thinking - Do please keep in mind that prior to about Xmas eve, the only thing I knew about Objective C and/or Cocoa could be summed up in *EXACTLY* two words. Those two words?
"It exists"
Maybe I'll "get there" some day, though :) (But not before I manage to scrape together enough spare pennies to upgrade to a newer kitty-cat than my current Panther, obviously...)
> >> You can take a passive approach and just query each menu to see what the > >> current selection is when you need it. If you do that, you probably [quoted text clipped - 10 lines] > You could implement it that way first, and consider putting in action > methods later in order to learn how to do that. Yep, that's what I've done for now.
> And, for the distant future, I'll just mention "Cocoa Bindings". This is > another way to keep things in sync, and it requires less code than the [quoted text clipped - 3 lines] > need a button marked "Do It". The result field gets automatically > updated whenever you change either of the input fields. I bumbled onto that concept somewhere about New Years Day, took one look, and fled screaming in abject terror... As I said above, "Maybe I'll get there some day" :)
Meanwhile, this is supposed to be a success report!
And I'm happy to report that I do indeed have exactly correct functionality! A few tweaks to the code in my previous message in the thread (and *PROPERLY* re-creating the project... I botched something in the first attempt, apparently) got me rolling.
Now I'm fully able to populate the menus (via the awakeFromNib method in my controller object) and my "convert" action (also in the controller) properly queries the individually.
One more comment: Back around New Year's Eve, I tried (and failed) with something *VERY* similar to what ended up working. The difference? In IB, I was trying to connect *FROM* the NSPopUpButtons *TO* the "MyController" instance, and failing every time because there was nothing to connect to. No matter what I did, I couldn't figure out how to connect the button to the controller. This time around, I went the other way - Connect *FROM* the controller *TO* the button, and everything works as planned. D'OH!
Thanks for the help, guys!
Just in case anybody's interested, here's what I ended up with in my controller class - a total of somewhere in the neighborhood of 65 lines of code, counting comments and vertical whitespace. I guess Objective C really CAN be compact!
/* MyController.h */
#import <Cocoa/Cocoa.h> #import "Converter.h"
@interface MyController : NSObject { IBOutlet Converter *converter; IBOutlet NSTextField *input; IBOutlet NSPopUpButton *Menu1; IBOutlet NSPopUpButton *Menu2; IBOutlet NSTextField *output; NSString *dataTimestamp; // holds the "generated on" timestamp NSMutableArray *myArray; // holds currency names and exchange rates } - (IBAction)Convert:(id)sender; - (void)awakeFromNib;
@end
And of course, the actual code... (I expect some lines are going to wrap - beware...)
/* MyController.m */ #import "MyController.h" #include "Converter.h" #import "RateSource.h" #import "CurrencyItem.h"
@implementation MyController
- (IBAction)Convert:(id)sender { // extract the source currency's dollars per unit value float DPU = [[myArray objectAtIndex:[Menu1 indexOfSelectedItem]] dollarsPerUnit]; // extract the target currency's units per dollar value float UPD = [[myArray objectAtIndex:[Menu2 indexOfSelectedItem]] unitsPerDollar]; // Convert the input value into dollars float DollarValue = [converter ConvertInputToDollars:[input floatValue] Rate:DPU]; // Convert the intermediate dollar value into the output value float OutputValue = [converter ConvertDollarsToOutput:DollarValue Rate:UPD];
// And finally, stuff the output field with the resulting number [output setFloatValue:OutputValue]; }
- (void)awakeFromNib { CFIndex counter; RateSource *rateSource = [RateSource New]; myArray = [NSMutableArray arrayWithCapacity:0]; // Hmmm... apparently I have to hold on to it via retain // If I don't, Convert barfs. [myArray retain]; // Get the data from the website dataTimestamp = [rateSource fillArray:myArray]; converter = [Converter New]; // Ditto - Convert barfs if I don't explicitly retain. Interesting... [converter retain]; // Now stuff the menus with the currency names from myArray. [Menu1 removeAllItems]; // Empty out IB's placeholder items [Menu2 removeAllItems]; // ditto for (counter = 0; counter < [myArray count]; counter++) { [Menu1 addItemWithTitle:[[myArray objectAtIndex:counter] name]]; [Menu2 addItemWithTitle:[[myArray objectAtIndex:counter] name]]; } } @end
 Signature Don Bruder - dakidd@sonic.net - If your "From:" address isn't on my whitelist, or the subject of the message doesn't contain the exact text "PopperAndShadow" somewhere, any message sent to this address will go in the garbage without my ever knowing it arrived. Sorry... <http://www.sonic.net/~dakidd> for more info
Dave Seaman - 12 Jan 2008 02:48 GMT > Meanwhile, this is supposed to be a success report! Great!
> And I'm happy to report that I do indeed have exactly correct > functionality! A few tweaks to the code in my previous message in the > thread (and *PROPERLY* re-creating the project... I botched something in > the first attempt, apparently) got me rolling.
> Now I'm fully able to populate the menus (via the awakeFromNib method in > my controller object) and my "convert" action (also in the controller) > properly queries the individually.
> One more comment: > Back around New Year's Eve, I tried (and failed) with something *VERY* [quoted text clipped - 4 lines] > controller. This time around, I went the other way - Connect *FROM* the > controller *TO* the button, and everything works as planned. D'OH! The rule is, you drag *from* the object that needs to find something, *to* the object that it needs to find. You wanted your controller to know about the popup, so you needed to drag *from* the controller *to* the popup.
It sometimes makes perfect sense to establish connections in both directions. When you dragged from the popup to the controller, IB thought you were trying to define the controller as the target of the popup. Only trouble is, you hadn't defined any suitable IBAction methods in the controller to connect to. If you were to define a sourceCurrencyDidChange: action, as we discussed, then this is exactly how you would make the connection.
 Signature Dave Seaman Oral Arguments in Mumia Abu-Jamal Case heard May 17 U.S. Court of Appeals, Third Circuit <http://www.abu-jamal-news.com/>
Gregory Weston - 12 Jan 2008 13:21 GMT > > Meanwhile, this is supposed to be a success report! > [quoted text clipped - 22 lines] > know about the popup, so you needed to drag *from* the controller *to* > the popup. Put another way, that may help reinforce the notion:
Messages are going to flow in the same direction you dragged the line. If you drag a line from a UI element to the controller, at runtime you'd expect the UI element to be asking the controller for stuff. If you dragged the other way, at runtime the controller will be injecting stuff into the UI.
G
Gregory Weston - 12 Jan 2008 13:39 GMT > > And, for the distant future, I'll just mention "Cocoa Bindings". This is > > another way to keep things in sync, and it requires less code than the [quoted text clipped - 7 lines] > look, and fled screaming in abject terror... As I said above, "Maybe > I'll get there some day" :) On first approach, Bindings can be a bit daunting. The easiest way, I think, to get your feet wet with them is to put together a preferences window and the use the NSUserDefaultsController class to actually manage its controls. It's fairly segregated from the rest of your app and to my mind gives a good illustration of what's going on without demanding too much of you up front.
> - (void)awakeFromNib > { [quoted text clipped - 5 lines] > // If I don't, Convert barfs. > [myArray retain]; Yep. Barring the case where you're using garbage collection (and you're not, given the tool versions you're using) there's a memory contract that's described here.
<http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Con cepts/ObjectOwnership.html#//apple_ref/doc/uid/20000043>
What it basically boils down to is that if you come into possession of an object via a method with the word string 'alloc' or 'copy' in its name, you own it and are responsible for releasing it. If you get the object by any other method, you aren't responsible for it and can't assume it's going to continue to be valid past (at most) the end of the current pass through the event loop. If you need to be able to rely on such an object, you'll send it a retain message.
Any object for which you are responsible (alloc'd, copied or retained) should, when you're done with it, be sent a 'release' or 'autorelease' message. If you send release, you should consider the object immediately invalid. It may not be technically, but it *is* unreliable at that point. If you send autorelease, it will (by default) last until the end of the current event loop pass.
If you're specifically creating an object that you intend to hold onto for a while, I'd suggest ...
myArray = [[NSMutableArray alloc] init];
... and eliminating the retain line.
> // Get the data from the website > dataTimestamp = [rateSource fillArray:myArray]; > converter = [Converter New]; > // Ditto - Convert barfs if I don't explicitly retain. Interesting... > [converter retain]; Not familiar with a method named 'New'. I'm familiar with 'new' which should be a shorthand for alloc/init and thus should be implicitly retained. I should've mentioned it above but it's largely out of favor as far as I'm aware and I forget it even exists.
G
Don Bruder - 12 Jan 2008 15:49 GMT <snip snip>
> Any object for which you are responsible (alloc'd, copied or retained) > should, when you're done with it, be sent a 'release' or 'autorelease' > message. If you send release, you should consider the object immediately > invalid. It may not be technically, but it *is* unreliable at that > point. If you send autorelease, it will (by default) last until the end > of the current event loop pass. "Event loop pass", not "While the object is in scope"? Apparently what little I've read on the topic hasn't been properly understood, then. More reading is in order, I guess.
> If you're specifically creating an object that you intend to hold onto > for a while, I'd suggest ... > > myArray = [[NSMutableArray alloc] init]; > > ... and eliminating the retain line. Which gives me back an array object in precisely what state? The way I'm doing it, I get exactly what I expect - An empty array that isn't going to vanish out from under me. Your way, I get the array, and it won't vanish, but I'm not (yet...) clear on what other setup I might need to do with it. Is it certain that it will be the same as what I get when I ask for it my way, or do I need to think of it as a chunk of "array clay" that I need to "pound into the exact shape I want"? My way, it comes "pre-shaped".
> > // Get the data from the website > > dataTimestamp = [rateSource fillArray:myArray]; [quoted text clipped - 6 lines] > retained. I should've mentioned it above but it's largely out of favor > as far as I'm aware and I forget it even exists. Probably haven't heard of it 'cause it's my own construct for making a new Converter instance. As in:
@interface Converter : NSObject { } + (Converter *)New; - (float)ConvertInputToDollars:(float)InputAmount Rate:(float)Exchange; - (float)ConvertDollarsToOutput:(float)Dollars Rate:(float)Exchange; @end
It just did a "Converter *newItem = [[super alloc] init]; return newItem;" operation, since there were no instance vars to worry about.
But, having said that, it's already "gone away" in a round of tinkering and tuning - I've realized that the whole "Converter" class can go *POOF* - It isn't needed except for illustrative purposes in the tutorial. I can easily accomplish what it does directly in the convert action of myController by (very slightly) altering two lines of already-existing code. As an added bonus, by doing that, I lose the overhead of sending two messages each pass, along with the one-time setup overhead - not a huge savings in this case, but not nonexistant - just non-significant except to someone like myself, who cut his programming teeth counting machine cycles in 6502 assembly language - Yeah, yeah, I know - I've got giga-cycles to burn, and RAM is cheap these days, etc, etc, but <shrug> "waste not, want not", right? :)
 Signature Don Bruder - dakidd@sonic.net - If your "From:" address isn't on my whitelist, or the subject of the message doesn't contain the exact text "PopperAndShadow" somewhere, any message sent to this address will go in the garbage without my ever knowing it arrived. Sorry... <http://www.sonic.net/~dakidd> for more info
Gregory Weston - 12 Jan 2008 17:54 GMT > <snip snip> > [quoted text clipped - 8 lines] > little I've read on the topic hasn't been properly understood, then. > More reading is in order, I guess. Prior to the release of 10.5, Cocoa code relied on manual memory management. The framework includes some convenience mechanisms and an standard idiom for their use that can greatly reduce the amount of *actual* manual intervention, to the point where it's near automatic for small-to-medium programs.
The core of that mechanism is the NSAutoreleasePool class. Instances of that class are (automatically) maintained in a stack. When you send autorelease to something derived from NSObject what happens is that it gets added to a list in the topmost pool in that stack. When that pool object is eventually deallocated, it sends a release message to everything in that list (and is popped off the stack).
The framework creates an autorelease pool at the start of the event loop and releases it at the end, so anything you didn't retain or create is effectively gone at the end of the loop. If you need to, you can create your own pools to help keep memory consumption down if, for example, you've got loops that create a lot of objects. Save that for later, though.
> > If you're specifically creating an object that you intend to hold onto > > for a while, I'd suggest ... [quoted text clipped - 4 lines] > > Which gives me back an array object in precisely what state? You own it. It will not go away unless you send it release directly or implicitly as the eventual result of an autorelease.
> The way I'm > doing it, I get exactly what I expect - An empty array that isn't going > to vanish out from under me. Your way, I get the array, and it won't > vanish, but I'm not (yet...) clear on what other setup I might need to > do with it. Nothing. It's *exactly* the same result, although as an implementation detail it will almost certainly have involved fewer instructions to achieve. (Which, reading further, should appeal to you.)
> But, having said that, it's already "gone away" in a round of tinkering > and tuning - I've realized that the whole "Converter" class can go > *POOF* - It isn't needed except for illustrative purposes in the > tutorial. I meant to mention in my first response: I'm not fond of that example code. It's laudable that they try to offer a simple example of what's going to be a new paradigm for a lot of people, but it's so contrived that I question how useful it really is. It's one of those: "Noone would actually do *that* that way" things.
> I can easily accomplish what it does directly in the convert > action of myController by (very slightly) altering two lines of [quoted text clipped - 5 lines] > Yeah, yeah, I know - I've got giga-cycles to burn, and RAM is cheap > these days, etc, etc, but <shrug> "waste not, want not", right? :) I started in the same place you did. My advice is "learn to let go." I know it's hard - it took me years - but it's the right thing to do. And when you are optimizing, the first step is to know where those cycles are actually going.
Gregory Weston - 12 Jan 2008 13:42 GMT > @interface MyController : NSObject > { [quoted text clipped - 28 lines] > // Ditto - Convert barfs if I don't explicitly retain. Interesting... > [converter retain]; Sorry. I just noticed something. If you've declared your Converter as an outlet and it exists for the life of the program, you can (and probably should) create it in your NIB file and just create the connection there. Then you don't need to worry about the last two lines above at runtime.
You can create arbitrary objects in the NIB by instantiating NSObject (or the closest stock ancestor of your class) and then using the inspector window to set the specific class you want it to be.
karthikeya.vh@gmail.com - 28 Feb 2008 06:04 GMT > > In article <fm6t6i$b9...@mailhub227.itcs.purdue.edu>, > ><snip for space> [quoted text clipped - 125 lines] > U.S. Court of Appeals, Third Circuit > <http://www.abu-jamal-news.com/> hi how to display the video in the window
Jolly Roger - 28 Feb 2008 10:14 GMT In article <7b6ccfc6-258b-413c-b7bd-1e80c3af4368@n58g2000hsf.googlegroups.com>,
> hi how to display the video in the window use the other method. thank you.
 Signature Note: Please send all responses to the relevant news group. If you must contact me through e-mail, let me know when you send email to this address so that your email doesn't get eaten by my SPAM filter.
JR
|
|
|