Working with Sheets in Cocoa
Pages: 1, 2, 3
You can see one of the practical differences between modal sessions and modal loops
simply in the execution flow in openPrefsSheet:. When we invoke beginSheet... a modal session is started, but the application goes on with executing openPrefsSheet:. However, at the point where we invoke runModalForWindow:, the application's event loop pauses while the indicated window runs in a modal loop. That is, not only is prefsWindow the only window that can accept events, but execution of our method stops until the modal loop ends. The application will remain in this state until some action in prefsWindow sends one of three messages to NSApp: stopModal, abortModal, stopModalWithCode:. This will be taken care of in the action method connected to our Close button-closePrefsSheet. Let's take a look at that method now.
The method closePrefsSheet does one simple thing for us -- indicate that the sheet should be closed by stopping the modal event loop for prefsWindow. All we do in closeMethod is the following:
- (IBAction)closePrefsSheet:(id)sender
{
[NSApp stopModal];
}
And with that our application resumes execution of the method openSheet at the point directly after where we started the modal event loop. Now -- back in openPrefsSheet: where we left off -- we simply send a couple of messages that ends the sheet's modal session, and removes it from the screen. endSheet: ends the document modal session began by beginSheet..., and orderOut: removes it from the window. The argument of orderOut: is the message sender, which in this case is self.
Try rebuilding now and see the results. You can download the completed project folder here. Your preferences sheet should look like the image below.
|
Because we only have one button in our window (other than the column-selector switches), the close button, stopModal, is sufficient for our purposes. If we had more than one button, however, than we could do several things to handle the different actions of different buttons. The obvious solution is to put the button-specific code in each button's action method. In that way, the button-specific code would be executed when its button is pushed, and each of these action methods would have a stopModal message to NSApp like we have above.
|
|
The other option is to have each button's action send a stopModalWithCode: message to NSApp, where the argument is an integer. What happens then, is that the integer passed as the argument to stopModalWithCode is returned by runModalForWindow:, and we can store that in a variable for later use. In this way, each button's action methods can take on a unique int identifier, allowing us to determine which button was pressed, and then executing code based on that.
In simple situations, it is probably better to just run whatever code you need within each button's action method, but I wanted to point out and make you aware of the available options.
As a fun aside, I want to tell you how to change the transparency of a window. You might have noticed that the transparency of our preferences window was changed as a result of it being a sheet. We could change it if we wanted to using one simple line of code, that we could put in awakeFromNib:
[prefsWindow setAlphaValue:0.50];
This would make a pretty transparent window, which is probably not so useful for a window with a lot of controls like we have. You can see the results of this in the image below:
|
That's it for now! That's how we create sheets. Not too difficult, I think, so have fun with it. This column is the last time we'll talk about AddressBook for a while. Next time, we'll begin our foray into Cocoa graphics, which will last for some time. See you next then!
Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.
Read more Programming With Cocoa columns.
Return to the Mac DevCenter.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 8 of 8.
-
Java Version - Solution
2002-06-02 19:53:46 jsumnertx [Reply | View]
Here's the Java solution to this example. I've only included the code that has changed since the last exercise.
Notes:
1) Creating a selector is different in Java.
2) NSApp variable doesn't exist but it's easy enough to derive...
-----
public void deleteRecord(Object sender) { /* IBAction */
String title = "Warning";
String defaultButton = "Delete";
String alternateButton = "Don't Delete";
String otherButton = null;
String message = "Are you sure you want to delete the selected record(s)?";
// here's how to make a selector in Java
NSSelector sel = new NSSelector("sheetDidEnd", new Class[] {NSWindow.class, int.class, Object.class} );
if (tableView.numberOfSelectedRows() == 0)
return;
NSApplication.beep();
NSAlertPanel.beginAlertSheet(title, defaultButton , alternateButton, otherButton, mainWindow, this, sel, null, null, message);
}
public void sheetDidEnd(NSWindow sheet, int returnCode, Object contextInfo)
{
if ( returnCode == NSAlertPanel.DefaultReturn ) {
NSMutableArray tempArray = new NSMutableArray();
NSEnumerator enumerator = tableView.selectedRowEnumerator();
Integer item;
item = (Integer)enumerator.nextElement();
while ( item != null ) {
tempArray.addObject(records.objectAtIndex(item.intValue()));
item = (Integer)enumerator.nextElement();
}
records.removeObjectsInArray(tempArray);
tableView.reloadData();
saveData(records);
}
}
public void openPrefsSheet(Object sender)
{
// NSApp variable doesn't appear to exist in Java. We can get it easily though
NSApplication nsapp=NSApplication.sharedApplication();
nsapp.beginSheet(prefsWindow, mainWindow, null, null, null);
nsapp.runModalForWindow(prefsWindow);
nsapp.endSheet(prefsWindow);
prefsWindow.orderOut(this);
}
public void closePrefsSheet(Object sender)
{
NSApplication.sharedApplication().stopModal();
}
-
Java Version - Solution
2004-04-25 19:14:10 raydreams [Reply | View]
I'm glad this guy is posting Java Cocoa versions. Someone ragged him in a previous post for doing so. I say, keep 'em coming, dude.
You know, when you look at the .NET documentation, MS provides examples at least in VB.NET and C#, and sometimes J# and Managed C++ as well. Why can't Apple do the same?
-
A problem with the beginSheet exemple
2001-12-26 03:24:43 juban [Reply | View]
This is a very usefull article. Thank you for this help.
However, there seems that the way you set-up sheets in your exemple didn't work for me.
You wrote :
- (IBAction)openPrefsSheet:(id)sender
{
[NSApp beginSheet:prefsWindow
modalForWindow:mainWindow
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
[NSApp runModalForWindow:prefsWindow];
[NSApp endSheet:prefsWindow];
[prefsWindow orderOut:self];
}
- (IBAction)closePrefsSheet:(id)sender
{
[NSApp stopModal];
}
When I tried this with my application, the sheet didn't want to close.
I tried to put a [prefsWindow close] statement before the [NSApp stopModal].
It worked better, but then, two sheets were appearing successively.
Finaly I tried this :
- (IBAction)openPrefsSheet:(id)sender
{
[NSApp beginSheet:prefsWindow
modalForWindow:mainWindow
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
}
- (IBAction)closePrefsSheet:(id)sender
{
[NSApp endSheet:prefsWindow];
[prefsWindow close];
}
I think that the runModalForWindow is not necessary for sheets because sheets are alredy modal windows.
In all cases, sheets are going away only if there is a "close" statement (in my case).
An other point is, if you have some updates to make to the window for which you want to attach a sheet,
you have to send a "display" message to your window and call the NSBeginAlertSheet like this :
NSBeginAlertSheet(title, defaultButton, alternateButton, otherButton, mainWindow, self, nil, @selector(sheetDidEnd:returnCode:contextInfo:), nil, message);
If you don't do so, your sheet won't slide down nicely as expected.
-
Extension: Sending email from Address Book
2001-10-17 15:34:16 mikenidel [Reply | View]
I've been sticking pretty closely to this tutorial so far, but I finally decided I felt intrepid enough to try extending the application a bit.
Specifically, I wanted to be able to select a row or rows from the address book, click a button, and open an email with the right recipient(s) in the To: field. Turns out it isn't too hard, once I found out how to open a URL. Here's how I did it, feel free to add to or comment on this approach.
In Interface Builder: Create an action in the Controller class called sendMail. Then add a button wherever you like and connect it to the controller, using the sendMail message as the target for the button. I added a "Send Email" button to the right of the "Delete" button in the main application window.
Once you have that connected up, add the line
- (IBAction)sendMail:(id)sender;
to your Controller.h file, and add the following code to the implementation in Controller.m:
- (IBAction)sendEmail:(id)sender
{
NSMutableString *url = [NSMutableString stringWithString:@"mailto:"];
NSString *str;
NSEnumerator *enumerator;
NSNumber *index;
enumerator = [tableView selectedRowEnumerator];
if((index = [enumerator nextObject])) {
str = [[records objectAtIndex:[index intValue]] objectForKey:@"Email"];
[url appendString:str];
while ( (index = [enumerator nextObject]) ) {
str = [[records objectAtIndex:[index intValue]] objectForKey:@"Email"];
[url appendString:@","];
[url appendString:str];
}
}
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:url]];
}
Note that if you're familiar with the mailto: URL format standard, you can make the new message open with a starting subject, body, and various other headers, using the query syntax. Try out "mailto:somebody@company.com?subject=MyApp&body=Hello" and find out more info about the mailto: URL options at
http://www.faqs.org/rfcs/rfc2368.html
Note that you have to encode any characters that aren't allowed in URLs - particularly blank spaces (%20) and such. I'm sure there are Cocoa methods to encode and decode URL strings, I just haven't found them yet.
Good Luck!
-schmeldog -
Extension: Sending email from Address Book
2002-01-02 21:28:53 michele [Reply | View]
Changed the code as follows :
// For sending mail
- (IBAction) sendMail: (id) sender
{
NSMutableString *url = [NSMutableString stringWithString: @"mailto:"];
NSString *str;
NSEnumerator *enumerator;
NSNumber *index;
// Check if there is a selected row
if ([tableView numberOfSelectedRows] == 0)
{
return;
}
// Enumerate all selected rows
enumerator = [tableView selectedRowEnumerator];
// Traverse the selected rows with enumerator
while ( ( index = [enumerator nextObject] ) )
{
// Retrieve the email field
str = [[records objectAtIndex: [index intValue]]
objectForKey: @"Email"];
// Add a comma to separate email addresses if this is not the first one
if (![url isEqualToString: @"mailto:"])
{
[url appendString: @","];
}
// Add the email address to the previously built string
[url appendString: str];
}
-
Extension: Sending email from Address Book
2001-10-26 12:07:56 rainwadj [Reply | View]
This is a great addition. I figured out how to clean up the string. When the Send Mail button is clicked, I have another sheet open with fields for To: (which is filled in with the email addresses from the selected records), Cc:, Bcc: and Subject:, and buttons for Send and Cancel. The action for the Send button on the sheet invokes this method:
- (IBAction)sendEmailMessage:(id)sender
{
NSMutableString *temp = [NSMutableString
stringWithString:@"mailto:"];
NSString *url;
[temp appendString:[emailToField stringValue]];
[temp appendString:@"?cc="];
[temp appendString:[emailCcField stringValue]];
[temp appendString:@"&bcc="];
[temp appendString:[emailBccField stringValue]];
[temp appendString:@"&subject="];
[temp appendString:[emailSubjField stringValue]];
url = (NSString *)
CFURLCreateStringByAddingPercentEscapes
(NULL, (CFStringRef)temp, NULL, NULL,
kCFStringEncodingISOLatin1);
[[NSWorkspace sharedWorkspace] openURL:
[NSURL URLWithString:url]];
[NSApp stopModal];
}
Everything gets put in the proper places in a new message window in Mail.app. Cool stuff.
-
run executable?
2001-10-06 10:22:58 psheldon [Reply | View]
What was apple's reason for distinguishing run executable as a command in os x 10.1 developer's tools? In particular, I think this evidences that we might run something else (whatever that is) and this raw surprise may evidence something so fundamental as to not yet be documented safely yet . I would understand if Apple doesn't want to answer , sometimes I have to play with something, we all do before understanding reasons .
Well, I thought simply asking the reason was brief enough to publish here .









Using the Downloaded copy of AddressBook 2, 3, and 4 I found the following:
2007-07-13 14:12:12.432 AddressBook[737] Exception raised during posting of notification. Ignored. exception: *** -[NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
I am new to Cocoa and do not quite understand the error?